EFPF in Version 2.0 erschienen

EFPF (Entity Framework Persistence Framework) ist ein Aufsatz auf dem Entity Framework, womit die Entwurfsmuster Repository und Unit of Work umgesetzt wurden. Somit können Standardoperationen auf realtionale Datenquellen (CRUD) ohne Aufwand verwendet werden. Weiterhin bietet EFPF eine integrierte Transaktionssteuerung und einen Provider zum Verwalten der Repositories. EFPF ist nun in Version 2.0 erschienen und bietet gegenüber der Version 1.0.* mehrere neue Ansätze und Verbesserungen. Da diese aber sehr tief in das System eingreifen, sind die Versionen nicht miteinander kompatibel. Ein Umstieg lohnt sich aber trotzdem.
Im nachfolgenden Klassendiagramm ist die neue Struktur zu erkennen.

EFPF Class diagram V2
Klassendiagramm von EFPF Version 2.0
Wie bisher bilden die Schnittstellen IRepository<T> und IUnitOfWork die Basis. Aber dann folgen schon die Änderungen. Das abstrakte Basisrepository ist durch ein allgemeines Repository ersetzt wurden, welches die Basisoperationen (CRUD; Create, Read, Update, Delete) bietet. Ein solches Repository muss also nicht mehr händisch erstellt werden. Hierzu kann das Interface IRepository<T> und die Klasse Repository<T> direkt verwendet werden. Neu ist auch, dass nun die UnitOfWork der zentrale Zugangspunkt für alle Datenoperationen ist. Da sich hierbei aber allgemeine Informationen mit anwendungsspezifischen mischen, muss eine Separierung erfolgen. Dazu ist es notwendig, dass Teile der UnitOfWork in der Anwendung implementiert werden müssen. Alle notwendigen Basisoperationen sind schon in EFPF implementiert. In der Anwendung muss es eine Schnittstelle geben, die von IUnitOfWork erbt. Hierin werden die Repositories als readonly Eigenschaft angegeben. Dann muss es noch eine Implementierung der UnitOfWork geben. Ich empfehle, von TransactionScopeUnitOfWork zu erben und die ensprechenden Interfaces zu implementieren. Hier ein Bespiel:

public interface IMyUoW : CSP.EFPF.Contracts.IUnitOfWork
{
    IPersonRepository Persons { get; }
    IRepository<Model.Adresse> Addresses { get; }
}

public class MyUnitOfWork : CSP.EFPF.TransactionScopeUnitOfWork<TestDbContext>, IMyUoW
{
    public MyUnitOfWork(CSP.EFPF.Contracts.IRepositoryProvider repoProvider)
        : base(repoProvider)
    {
    }

    public IPersonRepository Persons
    {
        get { return GetRepo<IPersonRepository>(); }
    }

    public IRepository<Model.Adresse> Addresses
    {
        get { return GetStandardRepo<Model.Adresse>(); }
    }

    protected override void ConfigContext()
    {
      //eventuelle Konfigurationen am Context
    }                
}

Die UoW ist also der zentrale Punkt für alle Zugriffe auf die Repositories. Ein Aufruf an ein alleinstehendes Repository ist nicht mehr möglich.
Hierzu gleich noch ein sehr wichtiger Hinweis. Da nun die gesamte Kontrolle in der UoW liegt, werden auch die Speicheranweisungen, also die eigentliche Persistierung der Änderungen in der Datenbank, über die UoW gesteuert. Dazu muss immer die Methode SaveChanges() aufgerufen werden. Ein Insert(), Update() oder Delete() speichert die Änderungen nicht automatisch. Die UoW verwendet somit explizites Speichern. Bei der Verwendung von Transaktionen wird vor dem CommitTransaction das Speichern aufgerufen. Hier ist der separate Aufruf nicht notwendig.

Um auf die Repositories zuzugreifen, gibt es zwei Methoden in der UnitOfWork. GetStandardRepo liefert immer den Typ IRepository<T>, also den Standardtyp mit den Basisoperationen (implementiert in Repository<T>). Mit GetRepo<T> können die „speziellen“ Repositories abgerufen werden. Spezielle Repositories sind Erweiterungen von IRepository<T> mit eigenen Implementierungen.
Die speziellen Repositories werden wie bisher auch erzeugt. Hierzu ein kleines Beispiel:

public interface IPersonRepository : IRepository<Person>
{
  //methods
}

public class PersonRepository : CSP.EFPF.Repository<Person>, IPersonRepository
{
  public PersonRepository(DbContext ctx)
      : base(ctx)
  {
  }
  
  //implementations
}

Da diese Logik aber schon vorgegeben ist, wird dieser Punkt nur interessant, wenn man eigene UoW schreiben möchte.

Nun gibt es aber ein Problem mit der Erzeugung der speziellen Repositories. Was vorher per Dependency Injection realisiert wurde, findet nun in einer Factory-Klasse statt. Im Klassendiagramm ist die abstrakte Factory RepositoryFactories zu erkennen. Von dieser muss eine abgeleitete Klasse erzeugt werden und die abstrakte Methode GetApplicationFactories implementiert werden. Darin wird ein Dictionary erzeugt, welches als Schlüssel den Typ des Repositories und als Wert eine Funktion enthält. Diese Funktion dient dem Instanziieren des Repositories. Da einem Respository immer der Datenbankkontext übergeben werden muss, wird dieser der Funktion von außen als Parameter überreicht. Das sieht dann zum Beispiel so aus.

class TestRepoFactories : CSP.EFPF.RepositoryFactories
{
  protected override IDictionary<Type, Func<DbContext, object>> GetApplicationFactories()
  {
    return new Dictionary<Type, Func<DbContext, object>>
      {
         {typeof(IPersonRepository), dbContext => new PersonRepository(dbContext)}
      };
  }
}

Abschließend geht es noch um die Erstellung der einzelnen Teile und deren Komposition. Das kann auf zwei Arten erfolgen. Entweder man erstellt alle Teile manuell und fügt diese im Code zusammen, oder man verwendet ein DI-Framework wie Ninject oder Autofac (oder ein beliebiges anderes). Ich empfehle auf jeden Fall die Erzeugung mit einem DI-Framework durchzuführen.
Hier mal der Vergleich der beiden Varianten im Code.

1. Manuell

var factories = new Data.TestRepoFactories();
var repoProvicder = new RepositoryProvider(factories);
var uow = new Data.MyUnitOfWork(repoProvicder);
_unitOfWork = uow;

2. DI-Framework (anhand von Ninject)

kernel.Bind<RepositoryFactories>().To<TestRepoFactories>()
                .InSingletonScope();
kernel.Bind<IRepositoryProvider>().To<RepositoryProvider>();
kernel.Bind<IMyUoW>().To<MyUnitOfWork>();

Die Verwendung ist dann ganz leicht. Hat man Zugriff auf die Instanz der UoW, sind darin alle notwendigen Methoden und alle Repositories als Eigenschaften (das ist selbst zu realisieren) vorhanden.
Hier ein beispiel der Verwendung.

[TestMethod]
public void TestInsertData()
{
    var actual = Model.Person.GetPersonInstance();

    _unitOfWork.Persons.Insert(actual);
    _unitOfWork.SaveChanges();

    var expected = _unitOfWork.Persons.GetOne(p => p.Name == actual.Name);

    Assert.IsNotNull(expected);
    Assert.AreEqual(expected.Name, actual.Name);
}

Vielen Dank an John Papa für die Inspiration zu dieser Umarbeitung. Einige Teile sind aus seinem Code Camper Projekt entliehen.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google Foto

Du kommentierst mit Deinem Google-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s