Archiv

Posts Tagged ‘Generic Repository’

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