L' Repository
abstraction et l' UnitOfWork
abstraction de l' OMI ont une place très précieuse dans tout développement significatif. Les gens discuteront des détails de la mise en œuvre, mais tout comme il existe de nombreuses façons d'écorcher un chat, il existe de nombreuses façons d'implémenter une abstraction.
Votre question est spécifiquement d'utiliser ou de ne pas utiliser et pourquoi.
Comme vous avez sans doute réalisé que vous avez déjà ces deux modèles intégrés dans Entity Framework, DbContext
est le UnitOfWork
et DbSet
est le Repository
. Vous n'avez généralement pas besoin de tester l'unité UnitOfWork
ou Repository
eux - mêmes car ils facilitent simplement entre vos classes et les implémentations d'accès aux données sous-jacentes. Ce que vous devrez faire, encore et encore, c'est vous moquer de ces deux abstractions lors du test unitaire de la logique de vos services.
Vous pouvez vous moquer, truquer ou autre chose avec des bibliothèques externes en ajoutant des couches de dépendances de code (que vous ne contrôlez pas) entre la logique faisant le test et la logique testée.
Donc, un point mineur est que d' avoir votre propre abstraction pour UnitOfWork
et Repository
vous donne un maximum de contrôle et de flexibilité lorsque vous vous moquez de vos tests unitaires.
Très bien, mais pour moi, le vrai pouvoir de ces abstractions est qu'elles fournissent un moyen simple d'appliquer les techniques de programmation orientée aspect et d'adhérer aux principes SOLID .
Donc vous avez votre IRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
Et sa mise en œuvre:
public class Repository<T> : IRepository<T>
where T : class
{
private readonly IDbSet<T> _dbSet;
public Repository(PPContext context)
{
_dbSet = context.Set<T>();
}
public T Add(T entity)
{
return _dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public IQueryable<T> AsQueryable()
{
return _dbSet.AsQueryable();
}
}
Rien d'extraordinaire jusqu'à présent, mais maintenant nous voulons ajouter un peu de journalisation - facile avec un décorateur de journalisation .
public class RepositoryLoggerDecorator<T> : IRepository<T>
where T : class
{
Logger logger = LogManager.GetCurrentClassLogger();
private readonly IRepository<T> _decorated;
public RepositoryLoggerDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
T added = _decorated.Add(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
return added;
}
public void Delete(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
_decorated.Delete(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable();
}
}
Tout est fait et sans modification de notre code existant . Il existe de nombreuses autres préoccupations transversales que nous pouvons ajouter, telles que la gestion des exceptions, la mise en cache des données, la validation des données ou autre et tout au long de notre processus de conception et de construction, la chose la plus précieuse que nous ayons qui nous permet d'ajouter des fonctionnalités simples sans changer aucun de notre code existant. est notre IRepository
abstraction .
Maintenant, j'ai souvent vu cette question sur StackOverflow - «comment faire fonctionner Entity Framework dans un environnement multi-locataire?».
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Si vous avez une Repository
abstraction, la réponse est "c'est facile d'ajouter un décorateur"
public class RepositoryTennantFilterDecorator<T> : IRepository<T>
where T : class
{
//public for Unit Test example
public readonly IRepository<T> _decorated;
public RepositoryTennantFilterDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
return _decorated.Add(entity);
}
public void Delete(T entity)
{
_decorated.Delete(entity);
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable().Where(o => true);
}
}
IMO, vous devez toujours placer une simple abstraction sur tout composant tiers qui sera référencé dans plus d'une poignée d'endroits. De ce point de vue, un ORM est le candidat parfait car il est référencé dans une grande partie de notre code.
La réponse qui me vient normalement à l'esprit lorsque quelqu'un dit «pourquoi devrais-je avoir une abstraction (par exemple Repository
) sur telle ou telle bibliothèque tierce» est «pourquoi pas vous?»
Les décorateurs PS sont extrêmement simples à appliquer à l'aide d'un conteneur IoC, tel que SimpleInjector .
[TestFixture]
public class IRepositoryTesting
{
[Test]
public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
{
Container container = new Container();
container.RegisterLifetimeScope<PPContext>();
container.RegisterOpenGeneric(
typeof(IRepository<>),
typeof(Repository<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryLoggerDecorator<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryTennantFilterDecorator<>));
container.Verify();
using (container.BeginLifetimeScope())
{
var result = container.GetInstance<IRepository<Image>>();
Assert.That(
result,
Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
Assert.That(
(result as RepositoryTennantFilterDecorator<Image>)._decorated,
Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
}
}
}