Je veux créer une unité de classe de travail qui enveloppe les référentiels de la même manière que this .
Le problème que j'ai est d'essayer de mettre en œuvre l'injection de dépendance en remplaçant les référentiels génériques dans l'exemple avec une interface IRepository. Dans l'article lié, ils utilisent des getters pour vérifier si le référentiel est instancié et si ce n'est pas le cas.
public GenericRepository<Department> DepartmentRepository
{
get
{
if (this.departmentRepository == null)
{
this.departmentRepository = new GenericRepository<Department>(context);
}
return departmentRepository;
}
}
Ce qui est fortement couplé.
Je peux voir deux façons de contourner cela.
Le problème avec 1 est que si j'injecte tous les référentiels, je dois instancier chaque référentiel même si je ne les utilise pas dans cette instance d'unité de travail particulière. Ainsi encourir la surcharge de le faire. J'imaginais utiliser une seule unité de classe de travail à l'échelle de la base de données, ce qui entraînerait beaucoup d'instanciations inutiles et un constructeur gigantesque.
Le problème avec 2 est qu’il serait facile d’oublier de définir et de finir avec des exceptions de référence nulles.
Existe-t-il des meilleures pratiques dans ce scénario? Et y a-t-il d'autres options que j'ai manquées?
Je commence juste à m'injecter de la dépendance et j'ai fait toutes les recherches que je peux trouver sur le sujet mais il se peut que je manque quelque chose de clé.
Une façon de procéder consiste à ne pas charger le UnitOfWork
de créer chaque Repository
par injection de conteneur, mais à le responsabiliser à chaque Repository
afin de s’assurer que le UnitOfWork
connaisse son existence lors de l’instanciation.
Cela garantira que
UnitOfWork
n'a pas besoin de changer pour chaque nouvelle Repository
Ceci est mieux démontré avec du code - j'utilise SimpleInjector donc les exemples sont basés sur ceci:
En commençant par l'abstraction Repository
:
public interface IRepository
{
void Submit();
}
public interface IRepository<T> :IRepository where T : class { }
public abstract class GenericRepository<T> : IRepository<T> where T : class { }
et le UnitOfWork
public interface IUnitOfWork
{
void Register(IRepository repository);
void Commit();
}
Chaque Repository
doit s'enregistrer avec le UnitOfWork
et vous pouvez le faire en modifiant la classe abstraite parent GenericRepository
pour vous assurer que c'est fait:
public abstract class GenericRepository<T> : IRepository<T> where T : class
{
public GenericRepository(IUnitOfWork unitOfWork)
{
unitOfWork.Register(this);
}
}
Chaque réel Repository
hérite de 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) { }
}
Ajoutez l'implémentation physique de UnitOfWork
et vous êtes tous ensemble:
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());
}
}
L'enregistrement de conteneur peut être configuré pour récupérer automatiquement toutes les instances définies de IRepository
et les enregistrer avec une étendue de durée de vie afin de s'assurer qu'elles survivent pendant toute la durée de votre transaction:
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);
}
}
Avec ces abstractions et une architecture construite autour de DI, vous avez un UnitOfWork
qui connaît tous les Repository
qui ont été instanciés dans tout appel de service et vous avez la validation de la compilation lorsque tous vos référentiels ont été définis. Votre code est ouvert pour extension mais fermé pour modification .
Pour tester tout cela, ajoutez ces classes
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();
}
}
Ajoutez ces lignes à BootStrapper.Configure()
//register the test classes
container.Register<SomeActivity>();
container.Register<MainActivity>();
Placez un point d'arrêt contre la ligne de code:
_repositories.ToList().ForEach(x => x.Value.Submit());
Et enfin, lancez ce code de test de la console:
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();
}
}
}
Vous constaterez que le code s'arrête au point d'arrêt et que vous avez une instance active de IRepository
prête et en attente de Submit()
toute modification apportée à la base de données.
Vous pouvez décorer votre UnitOfWork pour gérer les transactions, etc. Je vais maintenant céder la place au puissant .NetJunkie et vous recommander de lire ces deux articles ici et ici .
Au lieu d'injecter des instances de référentiel, injectez un seul objet de fabrique qui sera responsable de la création de ces instances. Vos accesseurs utiliseront ensuite cette usine.
Ma solution est UnitOfWork toujours responsable de la création du référentiel, mais j'ai créé une méthode GetRepository () factory dans UnitOfWork.
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>();
}
}