IMO sia l' Repository
astrazione che l' UnitOfWork
astrazione hanno un posto molto prezioso in ogni sviluppo significativo. Le persone discuteranno sui dettagli di implementazione, ma proprio come ci sono molti modi per scuoiare un gatto, ci sono molti modi per implementare un'astrazione.
La tua domanda è specificamente da usare o non usare e perché.
Come senza dubbio hai capito che hai già entrambi questi modelli integrati in Entity Framework, DbContext
è il UnitOfWork
ed DbSet
è il Repository
. In genere non è necessario eseguire un test unitario di UnitOfWork
o Repository
se stessi poiché stanno semplicemente facilitando tra le classi e le implementazioni di accesso ai dati sottostanti. Quello che ti ritroverai a dover fare, ancora e ancora, è deridere queste due astrazioni durante il test unitario della logica dei tuoi servizi.
Puoi deridere, falsificare o qualsiasi altra cosa con le librerie esterne aggiungendo strati di dipendenze di codice (che non controlli) tra la logica che esegue il test e la logica che viene testata.
Quindi un punto minore è che avere la tua astrazione per UnitOfWork
e Repository
ti dà il massimo controllo e flessibilità quando deridi i tuoi test unitari.
Tutto molto bene, ma per me, il vero potere di queste astrazioni è che forniscono un modo semplice per applicare le tecniche di programmazione orientata agli aspetti e aderire ai principi SOLID .
Quindi hai il tuo IRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
E la sua implementazione:
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();
}
}
Finora niente di straordinario, ma ora vogliamo aggiungere un po 'di registrazione - facile con un decoratore di registrazione .
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();
}
}
Tutto fatto e senza modifiche al nostro codice esistente . Ci sono numerose altre preoccupazioni trasversali che possiamo aggiungere, come la gestione delle eccezioni, la memorizzazione nella cache dei dati, la convalida dei dati o qualsiasi altra cosa e durante il nostro processo di progettazione e costruzione la cosa più preziosa che abbiamo che ci consente di aggiungere semplici funzionalità senza modificare il nostro codice esistente è la nostra IRepository
astrazione .
Ora, molte volte ho visto questa domanda su StackOverflow: "come si fa a far funzionare Entity Framework in un ambiente multi tenant?".
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Se hai Repository
un'astrazione, la risposta è "è facile aggiungere un decoratore"
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 dovresti sempre posizionare una semplice astrazione su qualsiasi componente di terze parti a cui verrà fatto riferimento in più di una manciata di posti. Da questo punto di vista, un ORM è il candidato perfetto in quanto è referenziato in gran parte del nostro codice.
La risposta che normalmente viene in mente quando qualcuno dice "perché dovrei avere un'astrazione (ad es. Repository
) Su questa o quella libreria di terze parti" è "perché non dovresti?"
I decoratori PS sono estremamente semplici da applicare utilizzando un contenitore IoC, come 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>)));
}
}
}