Archiv

Posts Tagged ‘Entity Framework’

Entity Framework 5.0 und 1 zu n Beziehungen

Normalerweise wird ja bei der Architektur darauf geachtet, dass man so generell wie möglich ist. Im konkreten Fall mit Listen, werden in sehr vielen Fällen IEnumerable als Typ genommen, damit man alle Klassen die diese Schnittstelle implementieren, auf diese Eigenschaft anwenden kann. Nun kann es aber sein, dass man in Zusammenhang mit Code-First das eine oder andere Phänomen beobachtet, nämlich die Schlüsselgenerierung in der Datenbank. Als Beispiel für diesen Blogpost soll eine kleine Adressverwaltung dienen bei denen eine Person mehrere Adressen haben kann. Einen interessanten Beitrag Finden Sie hier auf Stackoverflow.

Ausgangslage

Die Ausgangslage bildet ein relativ einfaches Modell, in welchem wir folgende Klassen haben:

  • Person
  • Address

Wobei gemäss Modell eine Person mehr als eine Adresse haben kann. Nun auf der Datenbank benötigt man hier auf der Seite der Adressen-Tabelle einen Schlüssel, der auf die Person zeigt, die mit dieser Adresse assoziiert ist.

AssociationAttribute

Wenn die Klasse Person wie folgt aussieht:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace AddressManager.Models
{
    ///
    /// Model class for a person.
    ///
    public class Person : PersistanceBase
    {
        ///
        /// Get's or set's the value for Firstname.
        ///
        [Required]
        public string Firstname { get; set; }
        ///
        /// Get's or set's the value for Lastname.
        ///
        [Required]
        public string Lastname { get; set; }
        ///
        /// Get's or set's the value for DateOfBirth.
        ///
        [DataType("datetime2")]
        public DateTime DateOfBirth { get; set; }
        ///
        /// Get's or set's the value for the assigned
        /// Addresses.
        ///
        public virtual IEnumerable<Address>Addresses { get; set; }
       ///
       /// Initializes a new instance of Person.
       ///
       public Person()
       {

       }
   }
}

dann fällt auf das die Adressen durch eine IEnumerable in Beziehung gebracht werden. Will man nun auf der Adresse eine Beziehung zur Person aufbauen, so muss in der Klasse Adresse eine weitere Eigenschaft hinzugefügt werden. Die Klasse Adresse sieht dann so aus:

using System.ComponentModel.DataAnnotations;

namespace AddressManager.Models
{
    ///
    /// Model class for an address
    /// associated to a person.
    ///
    public class Address : PersistanceBase
    {
        [Association("Address_Habitant", "Id", "Id", IsForeignKey = true)]
        public Person Habitant { get; set; }
        ///
        /// Get's or set's the value for Streetname.
        ///
        [Required]
        public string Streetname { get; set; }
        ///
        /// Get's or set's the value for Streetnumber.
        ///
        [Required]
        public int Streetnumber { get; set; }
        ///
        /// Get's or set's the value for Streetextension.
        ///
        public string Streetextension { get; set; }
        ///
        /// Get's or set's the value for Postalcode.
        ///
        [Required]
        public int Postalcode { get; set; }
        ///
        /// Get's or set's the value for City.
        ///
        [Required]
        public string City { get; set; }
        ///
        /// Initializes a new instance of Address.
        ///
        public Address()
        {

        }
    }
}

Wir müssen in diesem Fall eine zusätzliche Eigenschaft erstellen, die uns in der Datenbank die Beziehung darstellen lässt. Werden dann die Tabellen durch den DbContext generiert, so sieht die erstellte Datenbank und deren Tabellen wie folgt aus:

2013-09-19_Entity_Framework_Blogpost_01

Soweit alles gut, wir hoben die Anforderung erfüllt, dass einem Benutzer mehrere Adressen hinzugefügt werden können.

Automatische Generierung

Manchmal ist es ja ganz nett, wenn alles vollautomatisch gehen würde. In diesem Fall reicht nur die Wahl des entsprechenden Auflistungs-Typen. Damit Entity Framework die Beziehungen automatisch generiert, muss die Klasse Person nur wie folgt angepasst werden:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace AddressManager.Models
{
     ///
     /// Model class for a person.
     ///

     public class Person : PersistanceBase
     {
     ///
     /// Get's or set's the value for Firstname.
     ///
    [Required]
    public string Firstname { get; set; }
    ///
    /// Get's or set's the value for Lastname.
    ///
    [Required]
    public string Lastname { get; set; }
    ///
    /// Get's or set's the value for DateOfBirth.
    ///
    [DataType("datetime2")]
    public DateTime DateOfBirth { get; set; }
    ///
    /// Get's or set's the value for the assigned
    /// Addresses.
    ///
    public virtual ICollection<Address>Addresses { get; set; }
    ///
    /// Get's or set's the value for the assigned
    /// Phones.
    ///public virtual ICollection Phones { get; set; }
    ///
    /// Initializes a new instance of Person.
    ///
    public Person()
    {

    }
  }
}

Wie macht es eigentlich der Entity Framework Designer?

Genau gleich wie das oben beschrieben wurde. Es lohnt sich zuweilen bei den generierten Klassen von Microsoft nachzuschauen wie sie es machen. Natürlich ist nicht alles Gold was glänzt, aber man erspart sich viel Arbeit und Kopfzerbrechen.

Wir haben die IEnumerable Schnittstelle durch die Schnittstelle ICollection ersetzt. Lassen wir nun den DbContext unsere Datenbank generieren, so werden die Tabellen wie folgt erstellt:

2013-09-19_Entity_Framework_Blogpost_02

Der Klasse Adresse konnten wir die zusätzliche Eigenschaft, die wir notabene nur für die Beziehungsdarstellung zwischen Person und Adresse verwendet haben, entfernt werden.

Mein Fazit

Arbeite ich mit Entity Framework und Code-first, so werde ich in Zukunft immer anstelle der IEnumerable Schnittstelle die ICollection verwenden, da ich dann meinen Code, meiner Ansicht nach, nicht mit SQL spezifischen Attributen versehen muss, nur damit mir eine Beziehung erstellt wird.

Für Anregungen, Kritik aber auch Lob danke ich bereits im Voraus.
kick it on dotnet-kicks.de

Entity Framework ein pragmatischer Ansatz

23. Dezember 2011 2 Kommentare

Was meine ich damit?

Ganze einfach, da ich recht pragmatisch bin und nicht gerne all zu viel schreibe, dachte ich mir es muss doch eine Möglichkeit geben, dass ich genau eine Methode habe, die mir aufgrund eines übergebenen Typ’s die Resultate der Datenbank zurückliefert.

Normalerweise hätte ich das mit einer Vererbung gelöst.

  • 1.) Eine BaseEntität erstellen
  • 2.) Alle anderen Entitäten erben von dieser Basis-Entität.

So ist es mir möglich mit folgendem Code die entsprechenden Tabellen anzusprechen.

using(MyContext context = new Context())
{
      context.BaseEntity.OfType<Person>().ToList();
}

Nun der Ansatz ist nicht schlecht, hat aber seine Tücken, nähmlich dann wenn man grosse Datenmengen verarbeiten muss, dann muss er zuerst alle Daten die dem übergebenen Typ in der BaseEntität entsprechen laden und nochmals die der vererbten Entität.

Was wäre die Lösung?

Keine Vererbung. Gibt natürlich ein wenig mehr zu tun, man hat aber bei einem nicht bekannten Mengengerüst den Vorteil, dass die Datenbank schneller wachsen kann, ohne dass man ein Refactoring am Code vornehmen müsste.

Das ganze einfache Model sieht man nachstehend.


Jetzt kommt aber der entscheidende Punkt, den man beim Desing der Eintitäten beachten soll. Das Entity FrameWork kann mit dem EntitySet Namen umgehen. Wenn wir den EntitSet Namen gleich wie die Entität erfassen, so kann man ohne Probleme mit

 myEntity.GetType().Name; 

das EntitySet so angeben. Wenn man aber den generierten Namen lassen möchte, muss man bewusst sein, dass man sich dann den EntitySet-Name, bestehend aus dem Objekt-Typ Namen und einem Postfix vor einer Abfrage erstellen sollte.

Ich habe den ersten Weg gewählt und mir den EntitySet Namen gleichgesetzt wie der EntityName (im Grunde weiss ich ja dass die Tabelle Person heisst, die Menge aber Personen sind).


Wie sieht den nun der Ansatz aus?

Zu diesem Zweck habe ich mir einen ContextHelper erstellt. Interssant ist hier die Methode Get, die anhand eines ObjectQuery die Abfrage an den Kontext sendet.

Was brauchen wir dazu?

  • 1.) Ein ObjectQuery mit entsprechenden ObjectParametern dass sich aus dem übergebenen Objekt und dessen Eigenschaften zusammensetzt.
  • 2.) Ein string Query, welches die die Parameter aufnimmt und anhand des übergebenen Objekts erstellt wird.


Die Get Methode sind dann so aus:

        /// <summary>
        /// Get method for deleting a row in the database.
        /// </summary>
        /// <typeparam name="T">Type of object to get.</typeparam>
        /// <param name="entity">The search object which has to retrieve all matching entires in the database.</param>
        public static IEnumerable<T> Get<T>(T entity) where T : EntityObject
        {
            IEnumerable<T> result = null;
            using (BlogModelContainer container = new BlogModelContainer())
            {
                string queryCommand = string.Format("SELECT VALUE {1} FROM {0}.{1} WHERE 1=1", container.DefaultContainerName, entity.GetType().Name);

                // Creating the query, depending on how much of the properties in the given object are filled out
                // In this case only string properties will be read.
                queryCommand = CreateQueryString<T>(entity, queryCommand);

                ObjectQuery<T> entityQuery = new ObjectQuery<T>(queryCommand, container);
                CreateParameters<T>(entity).ForEach(entityQuery.Parameters.Add);

                result = entityQuery.ToList();
            }

            return result;
        }

Damit diese Methode bedient werden kann sind drei weitere notwendig.

  • 1.) Eine die nur PropertyInfo’s zurückgibt welche auch einen Wert haben.
  • 2.) Eine die den QueryString anhand der PropertyInfo’s abfüllt.

  • 3.) Last but not least eine Methode die anhand der PropertyInfo’s die ObjectParameter erstellt.

Die PropertyInfo Methode sieht wie folgt aus:

        /// <summary>
        /// Get's all properties from an object that where
        /// filled, or they have a value and returns it.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity to look for the properties.</param>
        /// <returns>List of propertyInfos (each propertyInfo has a value)</returns>
        private static List<PropertyInfo> GetProperty<T>(T entity) where T : EntityObject
        {
            List<PropertyInfo> result = new List<PropertyInfo>();

            entity.GetType().GetProperties().ToList().ForEach(property =>
            {
                if (property.PropertyType.Equals(typeof(string)))
                {
                    string propertyValue = property.GetValue(entity, new object[] { }) as string;

                    // Determining if the the property value of type string is filled out.
                    if (!string.IsNullOrWhiteSpace(propertyValue))
                    {
                        result.Add(property);
                    }
                }
            });

            return result;
        }

Da nur PropertyInfo’s zurückkommen die auch einen Wert aufweisen (momentan nur einen string-Typen, werde auch keine leeren Properties zurückgegeben.

Die Methode für den QueryString sieht dann so aus:

        /// <summary>
        /// Creates the appropriate querystring depending
        /// on the object that was passed.
        /// </summary>
        /// <typeparam name="T">The specific objectType</typeparam>
        /// <param name="entity">The entity to query against</param>
        /// <param name="queryString">The</param>
        /// <returns></returns>
        private static string CreateQueryString<T>(T entity, string queryString) where T : EntityObject
        {
            List<ObjectParameter> parameters = new List<ObjectParameter>();
            GetProperty<T>(entity).ForEach(p =>
            {
                string propertyValue = p.GetValue(entity, new object[] { }) as string;
                queryString += string.Format(" AND {0}.{1} = @{1}", entity.GetType().Name, p.Name);
            });

            return queryString;
        }

Für die ObjectParameter Erstellung wird eine ähnliche Methode verwendet:

        /// <summary>
        /// Creates the queryparameters to pass to the objectquery
        /// depending on the filled out properties in the parameter
        /// object.
        /// </summary>
        /// <typeparam name="T">Specific objectType which must be at least of type EntityObject</typeparam>
        /// <param name="entity">The entity with the filled properties for use in the query.</param>
        /// <returns>The ObjectParameters Array to add it to the ObjectQuery.</returns>
        private static List<ObjectParameter> CreateParameters<T>(T entity) where T : EntityObject
        {
            List<ObjectParameter> parameters = new List<ObjectParameter>();
            GetProperty<T>(entity).ForEach(p =>
            {
                string propertyValue = p.GetValue(entity, new object[] { }) as string;
                ObjectParameter parameter = new ObjectParameter(p.Name, propertyValue);
                parameters.Add(parameter);
            });

            return parameters;
        }

Somit werden die QueryParameter und der Query generisch anhand des „Suchobjektes“, das an die Get-Methode übergeben wird zusammengestellt.

Für Fragen, Kritik und Anregungen freue ich mich über einen Kommentar.

Ich wünsche allen frohe Festtage und einen guten Rutsch ins 2012, auf dass weider viele neue Sachen im .NET 4.5 endeckt werden können.

Das Beispielprojekt kann hier heruntergeladen werden.

kick it on dotnet-kicks.de