Iniezione di dipendenza in unità di modello di lavoro utilizzando i repository

Voglio creare un’unità di class di lavoro che avvolga gli archivi in ​​un modo simile a questo .

Il problema che sto avendo sta cercando di implementare l’integrazione delle dipendenze sostituendo i repository generici nell’esempio con un’interfaccia IRepository. Nella finestra dell’articolo collegato vengono utilizzati i getter per verificare se il repository viene istanziato e, in caso contrario, creare un’istanza.

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

Che è fortemente accoppiato.

Posso vedere due modi per aggirare questo.

  1. Utilizzare l’iniezione del costruttore.
  2. Utilizzare l’iniezione setter.

Il problema con 1 è che se inserisco tutti i repository, devo istanziare ogni repository anche se non li uso in quella particolare unità di istanza di lavoro. Così incorrere nel sovraccarico di farlo. Stavo immaginando di utilizzare una sola unità di class di lavoro, estesa al database, in modo che ciò portasse a molte istanze inutili e un gigantesco costruttore.

    Il problema con 2 è che sarebbe facile dimenticare di impostare e terminare con eccezioni di riferimento null.

    Esiste qualche sorta di best practice in questo scenario? E ci sono altre opzioni che ho perso?

    Sto solo facendo un’iniezione di dipendenza e ho fatto tutta la ricerca che posso trovare sull’argomento, ma potrei mancare qualcosa di chiave.

    Un modo per avvicinarsi a questo è non rendere il lavoro di UnitOfWork responsabile della creazione di ciascun Repository tramite l’iniezione di Container, ma invece di assumersi la responsabilità di ciascun Repository per assicurarsi che l’ UnitOfWork di UnitOfWork sappia della sua esistenza dopo l’istanziazione.

    Questo lo garantirà

    • il tuo UnitOfWork non ha bisogno di cambiare per ogni nuovo Repository
    • non stai usando un localizzatore di servizi (considerato da molti come un anti-pattern )

    Questo è meglio dimostrato con un po ‘di codice – Io uso SimpleInjector quindi gli esempi si basano su questo:

    A partire dall’astrazione del Repository :

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

    e UnitOfWork

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

    Ogni Repository deve registrarsi con UnitOfWork e ciò può essere fatto modificando la class genitore astratta GenericRepository per assicurarsi che sia fatta:

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

    Ogni vero Repository eredita dal GenericRepository :

     public class Department { } public class Student { } public class DepartmentRepository : GenericRepository { public DepartmentRepository(IUnitOfWork unitOfWork): base(unitOfWork) { } } public class StudentRepository : GenericRepository { public StudentRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { } } 

    Aggiungi l’implementazione fisica di UnitOfWork e tutto è pronto:

     public class UnitOfWork : IUnitOfWork { private readonly Dictionary _repositories; public UnitOfWork() { _repositories = new Dictionary(); } public void Register(IRepository repository) { _repositories.Add(repository.GetType().Name, repository); } public void Commit() { _repositories.ToList().ForEach(x => x.Value.Submit()); } } 

    La registrazione del contenitore può essere configurata per raccogliere automaticamente tutte le istanze definite di IRepository e registrarle con un ambito di IRepository per garantire che sopravvivano per tutta la durata della transazione:

     public static class BootStrapper { public static void Configure(Container container) { var lifetimeScope = new LifetimeScopeLifestyle(); container.Register(lifetimeScope); container.RegisterManyForOpenGeneric( typeof(IRepository<>), lifetimeScope, typeof(IRepository<>).Assembly); } } 

    Con queste astrazioni e un’architettura costruita attorno a DI hai un UnitOfWork che conosce tutti i Repository che sono stati istanziati all’interno di qualsiasi chiamata di servizio e hai la convalida del tempo di compilazione che tutti i tuoi repository sono stati definiti. Il tuo codice è aperto per estensione ma chiuso per modifica .

    Per testare tutto questo, aggiungi queste classi

     public class SomeActivity { public SomeActivity(IRepository departments) { } } public class MainActivity { private readonly IUnitOfWork _unitOfWork; public MainActivity(IUnitOfWork unitOfWork, SomeActivity activity) { _unitOfWork = unitOfWork; } public void test() { _unitOfWork.Commit(); } } 

    Aggiungi queste righe a BootStrapper.Configure()

     //register the test classs container.Register(); container.Register(); 

    Metti un punto di rottura contro la riga di codice:

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

    Infine, esegui questo codice di test della console:

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

    Troverai il codice che si ferma al punto di interruzione e hai una istanza triggers di un IRepository pronta e in attesa di Submit() qualsiasi modifica al database.

    Puoi decorare il tuo UnitOfWork per gestire le transazioni, ecc. A questo punto rimanderò al potente .NetJunkie e ti suggerisco di leggere questi due articoli qui e qui .

    Invece di iniettare istanze di repository, iniettare un singolo object factory che sarà responsabile della creazione di tali istanze. I tuoi getter useranno quella fabbrica.

    La mia soluzione è UnitOfWork ancora responsabile della creazione del repository ma ho creato un metodo factory GetRepository () in UnitOfWork per farlo.

     public interface IUnitOfWork : IDisposable { T GetRepository() 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() 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, 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(); } }