Archiv

Archive for the ‘Entity Framework’ Category

ASP.NET MVC 5 Paging selber implementiert

Bei meinem aktuellen Projekt, wollte ich das Paging in einer MVC Applikation selber, ohne Mithilfe der NuGet PagedList for MVC, realisieren.

Zuerst der Reihe nach

  1. Wie wird die erste Seite aufgerufen?
  2. Wie werden dann die folgenden Seiten aufgerufen?

Wie wird die erste Seite aufgerufen?

Zuerst schauen wir uns den HTML Markup des Linkes an, bei welchem wir zur entsprechenden Seite navigieren.


Wie ihr seht ist dass ein ganz normaler Querystring über http

Die Seite wird dann über folgende ActionMethode aufgerufen, wie auch die Seiten die dann folgen. Zum Beispiel Seite 2 etc.

Wie werden dann die folgenden Seiten aufgerufen?

            int toalPageCount = (context.Set<Skater>().Where(skater => skater.IsActive && skater.IsSEV).OrderBy(skater => skater.Lastname).Count() / 10);
            int pageCounter = 0;

            Dictionary<int, List<SkaterDTO>> pages = new Dictionary<int, List<SkaterDTO>>();
            List<SkaterDTO> DTO = new List<SkaterDTO>();                        

            if (selectedPage == 1)
            {
                context.Set<Skater>().OrderBy(skater => skater.Lastname).Where(skater => skater.IsActive && skater.IsSEV).Take(10).ToList().ForEach(skater =>
                {
                    var vm = new SkaterDTO(skater);
                    DTO.Add(vm);
                });
                pages.Add(toalPageCount, DTO.ToList());
                pageCounter++;                    
            }
            else if (selectedPage > 1)
            {
                if (selectedPage == toalPageCount)
                {
                    context.Set<Skater>().OrderByDescending(skater => skater.Lastname).Where(skater => skater.IsActive && skater.IsSEV).Take(10).ToList().ForEach(skater =>
                    {
                        var vm = new SkaterDTO(skater);
                        DTO.Add(vm);
                    });
                }
                else
                {
                    context.Set<Skater>().OrderBy(skater => skater.Lastname).Where(skater => skater.IsActive && skater.IsSEV).Skip(selectedPage * 10).Take(10).ToList().ForEach(skater =>
                    {
                        var vm = new SkaterDTO(skater);
                        DTO.Add(vm);
                    });
                }
                pages.Add(toalPageCount, DTO.ToList());
                pageCounter++;                                        
            }
                       

            return PartialView("Skaters", pages);

Die HTML Seite für die Darstellung des Resultates sieht dann wie folgt aus:

@model System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<EislaufSektionWebUI.DTO.SkaterDTO>>

@{
    ViewBag.Title = "Läufer(innen)";
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}

@Html.ActionLink("Zurück", "Welcome", "Home")

<table class="table table-responsive table-bordered table-hover">

        <thead>
            <tr>
                <th>
                    Vorname
                </th>
                <th>
                    Name
                </th>
                <th>
                    Bestandene Tests
                </th>
                <th>
                    SEV
                </th>
                <th>
                    Geburtsdatum
                </th>
            </tr>
        </thead>
        <tbody>
            @{               
                var currentPage = int.Parse(HttpContext.Current.Request.QueryString["selectedPage"]);
                foreach (var skater in Model[currentPage-1].ToList())
                {
                    <tr>
                        <td>
                            @skater.Firstname

                        </td>
                        <td>
                            @skater.Lastname
                        </td>
                        <td>
                            @skater.CertificationLevel
                        </td>
                        <td>
                            @skater.IsSEV
                        </td>
                        <td>
                            @skater.DateOfBirth
                        </td>
                    </tr>
                }
            }
        </tbody>
    </table>

<nav>
    <ul class="pagination">
        <li>
            <a href="#" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        @{
            foreach (var key in Model.Keys)
            {
                var number = key + 1;
                <li>
                    <a href="/Person/GetSkaters?selectedPage=@number">@number</a>
                </li>
            }
        }
        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>

Die Twitter Bootstrap „pagination“ Links werden dann wie folgt generiert:

<nav>
    <ul class="pagination">
        <li>
            <a href="#" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        @{
            foreach (var key in Model.Keys)
            {
                var number = key + 1;
                <li>
                    <a href="/Person/GetSkaters?selectedPage=@number">@number</a>
                </li>
            }
        }
        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>

Ich hoffe euch hat der Beitrag gefallen und freue mich über Rückmeldungen oder konstruktive Kritik.

Advertisements

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

Schritt für Schritt zum eigenen generischen Repository

Das Speichern und Abfragen von Daten wäre mit dem klassischen Repository-Pattern viel zu aufwändig für kleinere Projekte. In Anlehnung an das hier, in Verbindung mit dem Unit Of Work Pattern, verwendete generische Repository, habe ich mir überlegt, dass es doch für kleinere Projekte auch eine Lösung geben sollte, die nicht so viel Aufwand nach sich zieht.

Als Grundlage, oder besser gesagt als Ausgangspunkt habe ich mir diesen Artikel zur Brust genommen.

Wie wollen wir vorgehen?

  1. Wir erstellen uns ein ganz triviales Model (Adressverwaltung)
  2. Dann erstellen wir uns Schritt für Schritt das Repository
  3. Am Schluss ziehen wir noch ein Fazit aus dem Artikel
    1. Erstellung des Models

      In Anbetracht, dass nicht zwingend die Korrektheit der Datenmodellierung und der verwendeten Typen im Mittelpunkt steht, erstellen wir uns das Modell wie folgt.

      Das Modell unserer Adressverwaltung

      Wichtig an diesem Punkt ist, dass die Pluralisierung nicht geändert wird (hierzu später mehr bei der Erstellung des Repositories). Die Eigenschaften für die Personen-Entität sind hier represäntativ für alle anderen auch.

      Das Modell unserer Adressverwaltung

      Eine Spezialität ist die komplexe Eigenschaft Persistance, welch die Attribute für

      • CreateDate
      • ModifyDate
      • Creator
      • Modifier

      beherbergt. Schön wäre es ja, wenn alle Attribute bei der entsprechenden Operation des Repositories, Add, Update automatisch aktualisiert wird (dazu später mehr).

      Soweit so gut, wir haben unsere Modell für die 1001 Adressverwaltung erstellt. Wollen wir doch zum nächsten Schritt übergehen, die Erstellung des Repositories.

      Die Repository Erstellung

      Damit wir mit unserem Repository die CRUD Operation unterstützen können brauchen wir folgende Methoden:

      • Rumpf des Repositories
      • Add
      • Remove
      • Update
      • GetObjectById
      • GetObjectByQuery
      • Persistance Attribute automatisch erstellen /aktualisieren
      Rumpf des Repositories

      Damit wir die restlichen Methoden implementieren können müssen wir das Repository wie folgt erstellen:

      namespace GenericRepositoryPattern
      {
          public class Repository<T> : IDisposable where T: EntityObject
          {
              /// <summary>
              /// The context of the database.
              /// </summary>
              private readonly AddressModelContainer context;
              /// <summary>
              /// The related objectSet to the context. Is dependent
              /// of the context.
              /// </summary>
              private readonly ObjectSet<T> objectSet;
              /// <summary>
              /// Constructor to initialize the context and the
              /// objectSet.
              /// </summary>
              /// <param name="context">The context of the model.</param>
              public Repository(AddressModelContainer context)
              {
                  this.context = context;
      
                  if (context != null) 
                  { 
                      this.objectSet = context.CreateObjectSet<T>();
                      this.context.SavingChanges +=new EventHandler(context_SavingChanges);
                  }
              }
      

      (Anmerkung: Die Wiedergabe des Code’s ist gekürzt).
      Wichtig hierbei ist, dass der Context von aussen her dem Konstruktor übergeben werden muss und das ObjectSet vom instantiierten Context das ObjectSet erhält.

      Add Methode

      Damit wir ein neues Objekt zu unserem ObjectContex hinzufügen können müssen wir die Methode wie folgt implementieren.

              /// <summary>
              /// Add's a newly created entity to the
              /// objectSet
              /// </summary>
              /// <param name="entity">The newly created entity.</param>
              public void Add(T entity)
              {
                  this.objectSet.AddObject(entity);            
                  this.context.SaveChanges();                    
              }
      

      Also eine ganz einfache Sache. Als nächstes wenden wir uns der Remove Methode zu.

      Remove Methode

      Das Entfernen eines Objektes ist relativ rasch implementiert:

              public void Remove(T entity)
              {
                  if (entity != null)
                  {
                      this.objectSet.Attach(entity);
                      this.context.DeleteObject(entity);
                      this.context.SaveChanges();
                  }
              }
      

      Also keine Hexerei. Als nächstes wenden wir uns der Update Methode zu und schauen diese genauer an.

      Update Methode

      Die Update Methode zeigt sich in ihrer Ausprägung so:

              public void Update(T entity)
              {
                  this.objectSet.Attach(entity);
                  this.context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);        
                  this.context.SaveChanges();
              }
      

      Damit der Context weiss, dass sich was geändert hat muss die Entität an den Context wieder „Attached“ und im ObjectStateManager der EntityState auf Modified gestellt werden. Dann speichert der Context die Daten aktualisiert.

      GetObjectById

      Damit wir ein Object vom Typ Person erhalten, anhand der übergebenen Id, erstellen wir uns in der Methode einen EntityKey und einem KeyValuePair. Das Attribut dass die Id verkörpert heiss auch „Id“.

              public T GetById(Int32 id)
              {
                  string entitySet = string.Format(@"{0}Set", typeof(T).Name);
                  KeyValuePair<string, object> member = new KeyValuePair<string, object>("Id", id);
                  IEnumerable<KeyValuePair<string, object>> values = new KeyValuePair<string, object>[] { member };
      
                  EntityKey key = new EntityKey(entitySet, values);
      
                  return (T)this.context.GetObjectByKey(key);
              }
      

      Und hier sehen wir auch wieso wir die Pluralisierung PersonSet belassen haben. Damit der Context weiss in welchem Set er suchen soll müssen wir dieses übergeben. Das erreichen wird mit dem kleinen Codeschnipsel

      string entitySet = string.Format(@"{0}Set", typeof(T).Name);
      

      Danach wird der EntityKey erstellt und dem Context zur Abfrage übergeben. Das gefundene Object casten wird dann in den generischen Typ den wir übergeben haben.

      GetObjectByQuery

      Damit wir spezifisch ein Objekt, oder auch mehrere zurück erhalten können wir diese mit dem entsprechenden Methode und einer Func zurück holen. Als Beispiel: Wenn ich eine Person haben möchte bei welchem ich nur den Vornahmen und den Nachnamen haben möchte, dann lege ich beim Aufrufer der Methode folgende Funktion fest:

      Func<Person, bool> functionPerson = person => person.FirstName.ToLower().Equals("hans");
      

      Die kann dann der Methode übergeben werden und diese erstellt dann auch das Resultat anhand der übergebenen Methode.

              public IEnumerable<T> GetObjectByQuery(Func<T, bool> customQuery)
              {
                  if (customQuery == null)
                  {
                      throw new ArgumentNullException("parameter: customquery must be given by caller!");
                  }
      
                  List<T> result = this.objectSet.Where(customQuery).ToList();
                  result.ForEach(this.objectSet.Detach);
                  return result;
              }
      [/csharp]
      <p>Wichtig ist nur, dass die gefundenen Objekte vom Context getrennt werden, damit diese auch ausserhalb der Verwendung des Contextes verwendet werden können.</p>
      <h6>Persistance Attribute aktualisieren</h6>
      <p>Dem Leser wird nicht entgangen sein, dass im Konstruktor der Context mit seinem Ereignis SavingChanges verknüpft worden ist. Hier werden alle relevanten Attribute vom komplexen Property IPersistance aktualisiert oder erstellt. Die Ereignisbehandlung sieht dann so aus:</p>
      
              void context_SavingChanges(object sender, EventArgs e)
              {
                  this.context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).ToList().ForEach(entity => {
                      if (entity != null && entity.Entity != null && entity.Entity.GetType().GetProperty("Persistance") != null)
                      {
                          IPersistance persistance = CreatePersistance();
                          entity.Entity.GetType().GetProperty("Persistance").SetValue(entity.Entity, persistance, null);
                      }
                  });
      
                  this.context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).ToList().ForEach(entity =>
                  {
                      if (entity != null && entity.Entity != null && entity.Entity.GetType().GetProperty("Persistance") != null)
                      {
                          UpdatePersistance(entity.Entity);
                      }
                  });
              }
      

      Hier wird eigentlich nicht’s anderes gemacht, als das alle Entitäten die den Status Added oder Modified haben das Persistance komplexe Property angepasst. Für das sind dann die zwei Methoden zuständig.

              private IPersistance CreatePersistance()
              {
                  IPersistance persistance = new IPersistance();
                  persistance.CreateDate = DateTime.Now;
                  persistance.ModifyDate = DateTime.Now;
                  persistance.Creator = WindowsIdentity.GetCurrent().Name;
                  persistance.Modifier = persistance.Creator;
                  return persistance;
              }
      

      Wird verwendet wenn eine neue Entität persistiert wird und die nachfolgende wenn eine Entität geändert worden ist.

              private void UpdatePersistance(object entity)
              {
                  if (typeof(T).GetProperty("Persistance") != null)
                  {
                      IPersistance persistance = (IPersistance)typeof(T).GetProperty("Persistance").GetValue(entity, null);
                      if (persistance != null)
                      {
                          persistance.Modifier = WindowsIdentity.GetCurrent().Name;
                          persistance.ModifyDate = DateTime.Now;
                      }
                  }
              }
      

      Zur Übersicht noch der vollständige Code des gesamten Repositories.

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Data.Objects.DataClasses;
      using GenericRepositoryPattern.Model;
      using System.Data.Objects;
      using System.Data;
      using System.Security.Principal;
      using System.Reflection;
      
      namespace GenericRepositoryPattern
      {
          /// <summary>
          /// Generic repository.
          /// </summary>
          /// <typeparam name="T">For example it could be a model entity Person</typeparam>
          public class Repository<T> : IDisposable where T: EntityObject
          {
              /// <summary>
              /// The context of the database.
              /// </summary>
              private readonly AddressModelContainer context;
              /// <summary>
              /// The related objectSet to the context. Is dependent
              /// of the context.
              /// </summary>
              private readonly ObjectSet<T> objectSet;
              /// <summary>
              /// Constructor to initialize the context and the
              /// objectSet.
              /// </summary>
              /// <param name="context">The context of the model.</param>
              public Repository(AddressModelContainer context)
              {
                  this.context = context;
      
                  if (context != null) 
                  { 
                      this.objectSet = context.CreateObjectSet<T>();
                      this.context.SavingChanges +=new EventHandler(context_SavingChanges);
                  }
              }
              /// <summary>
              /// Get's a specific entity by it's key.
              /// </summary>
              /// <param name="id">The id from the object to retrieve.</param>
              /// <returns>The object if it's present in the db, or NULL if not existent.</returns>
              public T GetById(Int32 id)
              {
                  string entitySet = string.Format(@"{0}Set", typeof(T).Name);
                  KeyValuePair<string, object> member = new KeyValuePair<string, object>("Id", id);
                  IEnumerable<KeyValuePair<string, object>> values = new KeyValuePair<string, object>[] { member };
      
                  EntityKey key = new EntityKey(entitySet, values);
      
                  return (T)this.context.GetObjectByKey(key);
              }
              /// <summary>
              /// Allows the user to query the db with a custom
              /// function to retrieve data from the specific type
              /// of entity.
              /// </summary>
              /// <param name="customQuery">Created outisde by caller</param>
              /// <returns>A queryable of T with the possibility to treat it further.</returns>
              /// <example>
              /// Func<Person, bool> functionPerson = person => person.FirstName.ToLower().Contains("da")
              /// </example>
              public IEnumerable<T> GetObjectByQuery(Func<T, bool> customQuery)
              {
                  if (customQuery == null)
                  {
                      throw new ArgumentNullException("parameter: customquery must be given by caller!");
                  }
      
                  List<T> result = this.objectSet.Where(customQuery).ToList();
                  result.ForEach(this.objectSet.Detach);
                  return result;
              }
      
              /// <summary>
              /// Updates the values of the entity.
              /// </summary>
              /// <param name="entity">Updates a given entity with new values.</param>
              public void Update(T entity)
              {
                  this.objectSet.Attach(entity);
                  this.context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);        
                  this.context.SaveChanges();
              }
              /// <summary>
              /// Add's a newly created entity to the
              /// objectSet
              /// </summary>
              /// <param name="entity">The newly created entity.</param>
              public void Add(T entity)
              {
                  this.objectSet.AddObject(entity);            
                  this.context.SaveChanges();                    
              }
              /// <summary>
              /// Occurs every time an entity is saved or updated.
              /// It goes through the entity properties to update or craete
              /// the persistance attributes that are necessary for saving.
              /// </summary>
              /// <param name="sender">The context</param>
              /// <param name="e">The eventargs from the context.</param>
              void context_SavingChanges(object sender, EventArgs e)
              {
                  this.context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).ToList().ForEach(entity => {
                      if (entity != null && entity.Entity != null && entity.Entity.GetType().GetProperty("Persistance") != null)
                      {
                          IPersistance persistance = CreatePersistance();
                          entity.Entity.GetType().GetProperty("Persistance").SetValue(entity.Entity, persistance, null);
                      }
                  });
      
                  this.context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).ToList().ForEach(entity =>
                  {
                      if (entity != null && entity.Entity != null && entity.Entity.GetType().GetProperty("Persistance") != null)
                      {
                          UpdatePersistance(entity.Entity);
                      }
                  });
              }
              /// <summary>
              /// Set's the createdate to the datetime
              /// where the creation of the entity
              /// object has been executed.
              /// </summary>
              /// <param name="entity">The entity to create the IPersistance creator and update attributes.</param>
              private IPersistance CreatePersistance()
              {
                  IPersistance persistance = new IPersistance();
                  persistance.CreateDate = DateTime.Now;
                  persistance.ModifyDate = DateTime.Now;
                  persistance.Creator = WindowsIdentity.GetCurrent().Name;
                  persistance.Modifier = persistance.Creator;
                  return persistance;
              }
              /// <summary>
              /// Set's the modifydate to the datetime
              /// where the modification of the entity
              /// object has been executed.
              /// </summary>
              /// <param name="entity">The entity to update the IPersistance modify attributes.</param>
              private void UpdatePersistance(object entity)
              {
                  if (typeof(T).GetProperty("Persistance") != null)
                  {
                      IPersistance persistance = (IPersistance)typeof(T).GetProperty("Persistance").GetValue(entity, null);
                      if (persistance != null)
                      {
                          persistance.Modifier = WindowsIdentity.GetCurrent().Name;
                          persistance.ModifyDate = DateTime.Now;
                      }
                  }
              }
              /// <summary>
              /// Removes an entity from the database.
              /// </summary>
              /// <param name="entity">The entity to remove. Must be loaded prior to remove.</param>
              public void Remove(T entity)
              {
                  if (entity != null)
                  {
                      this.objectSet.Attach(entity);
                      this.context.DeleteObject(entity);
                      this.context.SaveChanges();
                  }
              }
      
              #region IDisposable Members
              /// <summary>
              /// Dispose the context.
              /// </summary>
              public void Dispose()
              {
                  if (this.context != null)
                  {
                      this.context.Dispose();
                  }
              }
      
              #endregion
          }
      }
      

      Fazit

      Mit dem hier vorgestellten Ansatz ist es uns , auch für kleinere Projekte möglich, rasch und einfach CRUD Methoden zur Verfügung zu stellen ohne, dass wir das Unit of Work und das Repositoriy Pattern in ihren grössten Ausprägungen implementieren müssen.

      Wenn Ihnen der Artikel gefallen hat, würde ich mich über einen KICK freuen. Auf Verbesserungsvorschläge und Kritik bin ich natürlich auch offen.

      kick it on dotnet-kicks.de

WCF DataContracts synchronisieren mit Entity Framework Klassen

Im Rahmen einer Projektarbeit haben wir das Ziel Businessdaten über die WCF zu transportieren. Die Daten sind im Entity Framework gespeichert. Schön wäre es wenn wir diese Entitäten direkt über den Kommunikationskanal schicken könnten. Leider ist dies nicht möglich da ein direktes Versenden der Entitäten, durch Projektrestriktionen nicht erlaubt ist, oder man ganz einfach keine Referenzen auf dem Client auf den Namespace System.Data haben möchte.

Also muss eine Lösung her die einigermassen hilfreich sein kann. Diese Lösung erledigt das Mapping eines einkommenden DatenVertrages mit der entsprechenden Entität.

Vorbereitungen
  • AddressManagement.BusinessModel
  • AddressManagement.PersistanceModel
  • AddressManagement.ServiceLayer

Für das Hosten der WCF können wir verschiedene Möglichkeiten nutzen, die aber in diesem Blogpost nicht abgedeckt werden. Es soll nur die Möglichkeit einer generischen Mapping Methode aufgezeigt werden, damit die Daten vom Entity Framework nicht direkt über den Kommunikationskanal versendet werden müssen, sondern gekapselt werden.

Wir erstellen uns ein Entity-Modell dass wie folgt aussieht: (Dieses erstellen wir im Projekt AddressManagement.Persistance)

Das Entity Data Design Model

Anschliessend erstellen wir uns unseres eigentliche Businessklassenmodell welches wir im Projekt AddressManagement.BusinessModel anlegen.

Das Businessklassen Model

Wichtig hierbei ist, dass die Beziehungen, Phones, Addresses genau gleich heissen wie im Entity Model. Auch alle anderen Eigenschaften müssen die gleiche Schreibweise haben, da sonst im Mapper keine Übereinstimmung gefunden wird.

Die Businessklassen sollen dann mit der WCF übertragen werden. Hierzu werden die Klassen mit den entsprechenden Attributen versehen.

Die Klasse Person

namespace AddressManagement.BusinessModel
{
    [DataContract]
    [KnownType(typeof(Address))]
    [KnownType(typeof(Phone))]
    public class Person : IPersistance
    {        
        #region IPersistance Members
        [DataMember]
        public Int32 Id { get; set; }
        #endregion
        [DataMember]
        public string FirstName { get; set; }
        [DataMember]
        public string LastName { get; set; }
        [DataMember]
        public IEnumerable<IPersistance> Addresses { get; set; }
        [DataMember]
        public IEnumerable<IPersistance> Phones { get; set; }
    }
}

Die Klasse Phone

namespace AddressManagement.BusinessModel
{
    [DataContract(IsReference = true)]
    [KnownType(typeof(ObjectType))]
    public class Phone : IPersistance
    {
        #region IPersistance Members
        [DataMember]
        public Int32 Id { get; set; }
        #endregion
        [DataMember]
        public string SubScriberNumber { get; set; }
        [DataMember]
        public ObjectType PhoneType { get; set; }
    }
}

Die Klasse Address

namespace AddressManagement.BusinessModel
{
    [DataContract(IsReference = true)]
    [KnownType(typeof(ObjectType))]
    public class Address : IPersistance
    {
        #region IPersistance Members
        [DataMember]
        public Int32 Id { get; set; }
        #endregion
        [DataMember]
        public string StreetName { get; set; }
        [DataMember]
        public Int16 StreetNumber { get; set; }
        [DataMember]
        public Int16 PostalCode { get; set; }
        [DataMember]
        public string CityName { get; set; }
        [DataMember]
        public ObjectType AddressType { get; set; }
    }
    [DataContract]
    public enum ObjectType
    {
        [EnumMember]
        Private,
        [EnumMember]
        Business
    }
}

Der Mapper/Synchronisierer

Damit wir nun beide Welten miteinander abgleichen können ist der Mapper zu erstellen, welche ich Schritt für Schritt erläutere. Wir erstellen uns als erstes eine Statische Klasse mit dem Namen DataContractToEntityMapper oder DataContractAndEntitySynchronizer und erstellen als erstes folgende Methode:

        /// <summary>
        /// Maps a given datacontract to the appropriate entity.
        /// Check's if the datacontract is new or must be updated
        /// and return the newly persisted contract back or it's updated
        /// values, if it was already persisted before.
        /// </summary>
        /// <typeparam name="T">Type of the datacontract</typeparam>
        /// <typeparam name="U">Type of the entity</typeparam>
        /// <param name="dataContract">The datacontract to interact with.</param>
        /// <returns>A new or updated datacontract from the database.</returns>
        public static IPersistance SynchronizeDataContractAndEntity<T, U>(T dataContract)
            where T : IPersistance
            where U : EntityObject
        {
            IPersistance persistance = Activator.CreateInstance<T>();
            // 1. a) Determine if the datacontract is already persisted.
            if (dataContract.Id == 0)
            {
                // 1. b) Map the newly created entity to the datacontract.                
                PersistDataContract<T, U>(dataContract);
            }
            else
            // 2.) Map an existing entity to it's datacontract pendant
            {
                U persistedEntity = Activator.CreateInstance<U>();
                using (AddressRepository<U> repository = new AddressRepository<U>(new AddressManagementPersistanceModelContainer()))
                {
                    persistedEntity = repository.GetByKey<U>(dataContract.Id);
                    persistance = UpdateProperties<T, U>(dataContract, persistedEntity);
                }
            }
            return persistance;
        }

Diese Methode führ folgende Aktionen durch:

  1. Existiert der eingetroffene DataContract schon in der Datenbank?
  2. Ist die Id 0, dann muss eine neue Entität angelegt werden
  3. Ist die Id > 0 ist der Contract bereits in der Datenbank vorhanden und muss mit den vorliegenden Werten aktualisiert werden.
  4. Zum Schluss, nachdem die Entity und der DataContract miteinander synchronisiert worden sind, wird ein aktualisierter DataContract zurück gegeben.

Als nächster Schritt erstellen wir uns eine Methode die für den Abgleich der Property-Werte zuständig ist. Diese unterscheidet ob es sich bei der Eigenschaft um eine Collection oder um eine einfache Eigenschaft handelt und führt dementsprechend die Aktualisierung aus:

        /// <summary>
        /// Updates the properties from a datacontract.
        /// </summary>
        /// <typeparam name="T">The type of datacontract</typeparam>
        /// <typeparam name="U">The type of the entity</typeparam>
        /// <param name="dataContract">The datacontract to update the values.</param>
        /// <param name="entity">The entity that is persisted and owns the actual values.</param>
        /// <returns>Updated datacontract.</returns>
        private static IPersistance UpdateProperties<T, U>(T dataContract, U entity)
            where T : IPersistance
            where U : EntityObject
        {
            IPersistance persistance = (IPersistance)CreateInstance<T, U>(dataContract, entity);
            entity.GetType().GetProperties().ToList().ForEach(entityPropertyInfo =>
            {
                string propertyName = entityPropertyInfo.Name;
                PropertyInfo dataContractPropertyInfo = persistance.GetType().GetProperty(propertyName);
 
                if (entityPropertyInfo != null && !entityPropertyInfo.PropertyType.Name.Contains(typeof(EntityCollection<U>).Name))
                {
                    var entityPropertyValue = entityPropertyInfo.GetValue(entity, null);
                    if (entityPropertyValue != null && dataContractPropertyInfo != null && !dataContractPropertyInfo.PropertyType.IsEnum)
                    {                        
                        dataContractPropertyInfo.SetValue(persistance, entityPropertyValue, BindingFlags.Default, null, null, null);
                    }
                }
                else if (entityPropertyInfo != null && dataContract.GetType().GetProperty(entityPropertyInfo.Name) != null)
                {
                    var ienumerableValues = (IEnumerable)HandleRelatedEnds<T>(dataContract, (IEnumerable)entityPropertyInfo.GetValue(entity, null));
                    persistance.GetType().GetProperty(entityPropertyInfo.Name).SetValue(persistance, ienumerableValues, BindingFlags.Default, null, null, null);
                }
            });
            return persistance;
        }

Der erste Teil aktualisiert alle flachen Eigenschaften des DatenContracts. Der zweite Teil kümmert sich um die Collections.

        /// <summary>
        /// Handles the collections an creates for the datacontract
        /// appropriate items to add them to the list.
        /// </summary>
        /// <typeparam name="T">Type of datacontract</typeparam>
        /// <param name="dataContract">The specific contract where the list belongs to.</param>
        /// <param name="entityCollectionToDuplicate">The entitycollection to map to the datacontract.</param>
        /// <returns>A List of IPersistance objects (BusinessModel Objects).</returns>
        private static List<IPersistance> HandleRelatedEnds<T>(T dataContract, IEnumerable entityCollectionToDuplicate) where T : IPersistance
        {
            List<IPersistance> persistances = new List<IPersistance>();
            IEnumerator it = entityCollectionToDuplicate.GetEnumerator();
 
            while (it.MoveNext())
            {
                EntityObject entity = ((EntityObject)it.Current);
                IPersistance ipersistance = CreateInstance<T, EntityObject>(dataContract, entity);
                ipersistance = UpdateProperties<T, EntityObject>(dataContract, entity);
                persistances.Add(ipersistance);
            }
 
            return persistances;
        }

Damit die Items erstellt werden können braucht es eine weitere Hilfsmethode die uns entsprechende Instanzen zurück gibt.

        /// <summary>
        /// This method creates a specific instance from the Businessmodell Assembly
        /// given by the entity type.
        /// </summary>
        /// <typeparam name="T">The IPersistanceObject to create</typeparam>
        /// <typeparam name="U">The entity that contains the values</typeparam>
        /// <param name="dataContract">The datacontract to add /remove or update</param>
        /// <param name="entity">The entity that was created /updated depending on the passed datacontract.</param>
        /// <returns></returns>
        private static IPersistance CreateInstance<T, U>(T dataContract, U entity) where T : IPersistance
        {
            IPersistance persistance;
            string typeOfEntity = entity.GetType().Name;
            Assembly assembly = Assembly.GetAssembly(typeof(IPersistance));
            Type type = assembly.GetTypes().FirstOrDefault(assemblyType => assemblyType.Name.ToLower().Equals(typeOfEntity.ToLower()));
            persistance = (IPersistance)assembly.CreateInstance(type.FullName);
 
            return persistance;
        }

[/csharp]
<p>Der Funktionsumfang der oben beschriebenen Methode ist einfach. Nimm den Entity-Namen und suche in der BusinessModel Library nach dem entsprechenden Typ und instanziiere einen solchen und gib diesen dann zurück, damit er in der aufrufenden Methode in die Liste eingefügt werden kann.
Eine weitere Methode brauchen wir, damit wir einen DataContract, der über keinen Wert in der Id verfügt, persistieren können. (Das Repository ist nicht Teil dieses).
</p>

        /// <summary>
        /// A newly passed datacontract with it's properties
        /// will be persisted in the entity framework.
        /// </summary>
        /// <typeparam name="T">DataContractType</typeparam>
        /// <typeparam name="U">EntityType (must macht with the datacontract type)</typeparam>
        /// <param name="dataContract">The datacontract to persist.</param>
        private static void PersistDataContract<T, U>(T dataContract) where T : IPersistance where U : EntityObject
        {
            U entityToPersist = Activator.CreateInstance<U>();
            entityToPersist.GetType().GetProperties().ToList().ForEach(dataContractPropertyInfo =>
            {
                string propertyName = dataContractPropertyInfo.Name;
                PropertyInfo entityPropertyInfo = dataContract.GetType().GetProperty(propertyName);
 
                if (entityPropertyInfo != null && entityPropertyInfo.PropertyType != (typeof(RelatedEnd)))
                {
                    var entityPropertyValue = entityPropertyInfo.GetValue(dataContract, null);
                    if (entityPropertyValue != null)
                    {
                        dataContractPropertyInfo.SetValue(entityToPersist, entityPropertyValue, BindingFlags.Default, null, null, null);
                    }
                }
            });
 
            using (AddressRepository<U> repository = new AddressRepository<U>(new AddressManagementPersistanceModelContainer()))
            {
                repository.Add(entityToPersist);
                repository.SaveChanges();
            }
        }

Mit dieser letzten Methode haben wir den Mapper erstellt. Nun ist es uns möglich von einem Sender ein Objekt der Businessklassen zu erstellen und müssen uns keine Sorgen machen, ob dies bereits persistiert worden ist oder nicht. Wir erhalten als dann die aktualisierten oder neuen Werte von der Datenbank als Resultat zurück und können somit auf dem Client weiter arbeiten.

Fazit

Der Mapper hat nicht den Anspruch 100% aller Fälle abzudecken, er versucht zu zeigen wie man eine grösstmögliche Modularisierung anstreben kann, ohne dass auf dem Client Server DLL’s refernziert werden müssen. Und natürlich ein nicht zu unterschätzender Punkt ist der Aufwand einmal für die WCF Seite und einmal für die Persistance-Seite ein Modell zu führen.

Sollte Ihnen der Artikel gefallen haben, würde ich mich über einen kick freuen. Gerne beantworte ich auch Fragen und Kritik.

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

Entity Framework 4.0

Nachdem ich in meiner Weiterbildung am Integrations-Projekt die Aufgabe gefasst habe die Persistenz mittels Entity Framework 4.0 zu implementieren, habe ich mich ein wenig damit befasst. Nun meistens ist es ja so dass man in der Vererbungshierarchie ein GrundObjekt haben möchte dass die folgenden Attribute speichert:

– ErstellungsDatum

– Ersteller

Damit ich diese Eigenschaften nicht immer bei jedem Objekt angeben möchte wenn ich Daten persistieren möchte habe ich mir den Vorteil, dass alle Entity Framework 4.0 generierten Klassen partial sind zu Nutze gemacht. Mein Modell sieht im EF 4.0 so aus:

EntityDesignerDiagram Damit ich nun die Eigenschaften ErstellungsDatum (CreateDate) und den Ersteller (Creator) automatisch speichern kann, muss ich für die BaseEntity eine weitere Klasse im gleichen Namespace erstellen.

image Hier habe ich den Einstiegspunkt dazu gefunden. Die BaseEntity.cs muss von EntityObject erben. In der partiellen Klasse habe ich den parameterlosen Konstruktor verwendet und die Werte den Felder zugewiesen, da die Eigenschaften im EF Model private sind.

Creator Attribut

CreateDate Attribut

image

image

BaseEntity.cs

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Data.Objects.DataClasses;
   6: using System.Security.Principal;
   7:  
   8: namespace PersonenManagement.Model
   9: {
  10:     public partial class BaseEntity : EntityObject
  11:     {
  12:         public BaseEntity()
  13:         {
  14:             this._Creator = WindowsIdentity.GetCurrent().Name;
  15:             this._CreateDate = DateTime.Now;
  16:         }        
  17:     }
  18: }

Für Kritik und Verbesserungsvorschläge bin ich natürlich immer zu haben.

Kategorien:Entity Framework