wake-up-neo.com

Abhängigkeitsinjektion in Arbeitseinheit mit Repositorys

Ich möchte eine Einheit der Arbeiterklasse erstellen, die Repositorys auf ähnliche Weise wie this umgibt. 

Das Problem, das ich habe, ist der Versuch, die Abhängigkeitsinjektion zu implementieren, indem die generischen Repositorys im Beispiel durch eine IRepository-Schnittstelle ersetzt werden. In der Uow im verlinkten Artikel überprüfen sie mit Hilfe von Gettern, ob das Repository instanziiert ist, und wenn nicht, dann instanziiert es.

public GenericRepository<Department> DepartmentRepository
{
    get
    {
        if (this.departmentRepository == null)
        {
            this.departmentRepository = new GenericRepository<Department>(context);
        }
        return departmentRepository;
    }
}

Welches ist stark gekoppelt.

Ich kann zwei Wege sehen.

  1. Konstruktor-Injektion verwenden.
  2. Setterinjektion verwenden.

Das Problem mit 1 besteht darin, dass ich jedes Repository instanziieren muss, wenn ich alle Repositorys injiziere, selbst wenn ich sie nicht in dieser bestimmten Arbeitseinheit verwende. Dies verursacht den Aufwand, dies zu tun. Ich stellte mir vor, eine datenbankweite Einheit der Arbeiterklasse zu verwenden, so dass dies zu unnötiger Instanziierung und einem gigantischen Konstruktor führen würde.

Das Problem bei 2 ist, dass es leicht ist zu vergessen, Nullreferenzausnahmen festzulegen.

Gibt es in diesem Szenario Best Practices? Und gibt es noch andere Optionen, die ich vermisst habe?

Ich komme gerade in die Abhängigkeitsinjektion und habe alles getan, was ich zu diesem Thema finden kann, aber mir fehlt vielleicht ein Schlüssel.

33
rashleighp

Ein Ansatz, um dies zu erreichen, besteht darin, die UnitOfWork nicht für die Erstellung jeder Repository-Datei durch die Container-Injektion verantwortlich zu machen, sondern dafür zu sorgen, dass die Repository-Instanz dafür verantwortlich ist, dass die UnitOfWork-Instanz bei der Instantiierung über ihre Existenz verfügt.

Dies wird dafür sorgen

  • ihr UnitOfWork muss nicht für jeden neuen Repository geändert werden.
  • sie verwenden keinen Service Locator (wird von vielen als Anti-Pattern betrachtet)

Dies wird am besten mit etwas Code demonstriert - ich benutze SimpleInjector , so dass die Beispiele hierauf basieren:

Beginnend mit der Repository-Abstraktion:

public interface IRepository 
{
    void Submit();
}
public interface IRepository<T> :IRepository where T : class { }
public abstract class GenericRepository<T> : IRepository<T> where T : class { }

und die UnitOfWork

public interface IUnitOfWork
{
    void Register(IRepository repository);
    void Commit();
}

Jeder Repositorymust registriert sich selbst bei UnitOfWork. Dies kann durch Ändern der abstrakten übergeordneten Klasse GenericRepository erfolgen, um sicherzustellen, dass dies ausgeführt wird:

public abstract class GenericRepository<T> : IRepository<T> where T : class
{
    public GenericRepository(IUnitOfWork unitOfWork)
    {
        unitOfWork.Register(this);
    }
}

Jede echte Repository erbt von der GenericRepository:

public class Department { }
public class Student { }

public class DepartmentRepository : GenericRepository<Department> 
{
    public DepartmentRepository(IUnitOfWork unitOfWork): base(unitOfWork) { }
}

public class StudentRepository : GenericRepository<Student>
{
    public StudentRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { }
}

Fügen Sie in der physischen Implementierung von UnitOfWork hinzu und Sie sind fertig:

public class UnitOfWork : IUnitOfWork
{
    private readonly Dictionary<string, IRepository> _repositories;
    public UnitOfWork()
    {
        _repositories = new Dictionary<string, IRepository>();
    }

    public void Register(IRepository repository)
    {
        _repositories.Add(repository.GetType().Name, repository);
    }

    public void Commit()
    {
        _repositories.ToList().ForEach(x => x.Value.Submit());
    }
}

Die Containerregistrierung kann so eingerichtet werden, dass automatisch alle definierten Instanzen von IRepository abgerufen und mit einem Gültigkeitsbereich registriert werden, um sicherzustellen, dass alle über die gesamte Laufzeit Ihrer Transaktion erhalten bleiben:

public static class BootStrapper
{
    public static void Configure(Container container)
    {
        var lifetimeScope = new LifetimeScopeLifestyle();

        container.Register<IUnitOfWork, UnitOfWork>(lifetimeScope);

        container.RegisterManyForOpenGeneric(
            typeof(IRepository<>),
            lifetimeScope,
            typeof(IRepository<>).Assembly);
    }
}

Mit diesen Abstraktionen und einer Architektur, die auf DI basiert, haben Sie eine UnitOfWork, die alle Repositorys kennt, die innerhalb eines Serviceaufrufs instanziiert wurden, und Sie haben eine Kompilierzeitüberprüfung, bei der alle Ihre Repositorys definiert wurden. Ihr Code ist offen für die Erweiterung, aber zur Änderung geschlossen .

Um dies alles zu testen, fügen Sie diese Klassen hinzu

public class SomeActivity
{
    public SomeActivity(IRepository<Department> departments) { }
}

public class MainActivity
{
    private readonly IUnitOfWork _unitOfWork;
    public MainActivity(IUnitOfWork unitOfWork, SomeActivity activity) 
    {
        _unitOfWork = unitOfWork;
    }

    public void test()
    {
        _unitOfWork.Commit();
    }
}

Fügen Sie diese Zeilen zu BootStrapper.Configure() hinzu.

//register the test classes
container.Register<SomeActivity>();
container.Register<MainActivity>();

Setzen Sie einen Haltepunkt gegen die Codezeile:

_repositories.ToList().ForEach(x => x.Value.Submit());

Führen Sie schließlich diesen Konsolen-Testcode aus:

class Program
{
    static void Main(string[] args)
    {
        Container container = new Container();
        BootStrapper.Configure(container);
        container.Verify();
        using (container.BeginLifetimeScope())
        {
            MainActivity entryPoint = container.GetInstance<MainActivity>();
            entryPoint.test();
        }
    }
}

Sie werden feststellen, dass der Code am Haltepunkt stoppt, und Sie haben eine aktive Instanz von IRepository bereit, die auf Submit() wartet, um Änderungen an der Datenbank vorzunehmen.

Sie können Ihr UnitOfWork für Transaktionen usw. dekorieren. Ich werde an dieser Stelle auf den mächtigen .NetJunkie verzichten und empfehlen, diese beiden Artikel hier und hier zu lesen.

46
qujck

Statt Repository-Instanzen zu injizieren, fügen Sie ein einzelnes Factory-Objekt ein, das für die Erstellung dieser Instanzen verantwortlich ist. Ihre Getter verwenden dann diese Fabrik.

5
Ladislav Mrnka

Meine Lösung ist UnitOfWork, die immer noch für die Erstellung des Repositorys verantwortlich ist, aber ich habe dafür eine Factory-Methode GetRepository () in UnitOfWork erstellt.

public interface IUnitOfWork : IDisposable
{
    T GetRepository<T>() where T : class;
    void Save();
}

public class UnitOfWork : IUnitOfWork
{
    private Model1 db;

    public UnitOfWork() :  this(new Model1()) { }

    public UnitOfWork(TSRModel1 dbContext)
    {
        db = dbContext;
    }

    public T GetRepository<T>() where T : class
    {          
        var result = (T)Activator.CreateInstance(typeof(T), db);
        if (result != null)
        {
            return result;
        }
        return null;
    }

    public void Save()
    {
        db.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                db.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class TestRepository : GenericRepository<Test>, ITestRepository
{
    public TestRepository(Model1 db)
       : base(db)
    {
    }
}

public class TestManager: ITestManager
{
    private IUnitOfWork unitOfWork;
    private ITestRepository testRepository;
    public TestManager(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
        testRepository = unitOfWork.GetRepository<TestRepository>();
    }

}
0
sonmt