Contesto/Domanda
Ho lavorato su numerosi progetti .NET che sono stati necessari per salvare i dati e che hanno di solito finito per usare uno schema Repository. Qualcuno sa di una buona strategia per rimuovere tutto il codice boilerplate senza sacrificare la scalabilità del codice?C# - composizione oggetto - Rimozione Codice Boilerplate
Inheritance strategia
Perché così gran parte del codice repository è piatto della caldaia e deve essere ripetuto io normalmente creare una classe di base per coprire i principi fondamentali come la gestione delle eccezioni, la registrazione e il supporto delle transazioni, nonché alcuni CRUD metodi:
public abstract class BaseRepository<T> where T : IEntity
{
protected void ExecuteQuery(Action query)
{
//Do Transaction Support/Error Handling/Logging
query();
}
//CRUD Methods:
public virtual T GetByID(int id){}
public virtual IEnumerable<T> GetAll(int id){}
public virtual void Add (T Entity){}
public virtual void Update(T Entity){}
public virtual void Delete(T Entity){}
}
Quindi questo funziona bene quando ho un dominio semplice, posso creare rapidamente una classe di repository DRY per ogni entità. Tuttavia, questo inizia a scomparire quando il dominio diventa più complesso. Diciamo che è stata introdotta una nuova entità che non consente aggiornamenti. Posso suddividere le classi di base e spostare il metodo di aggiornamento in una classe diversa:
public abstract class BaseRepositorySimple<T> where T : IEntity
{
protected void ExecuteQuery(Action query);
public virtual T GetByID(int id){}
public virtual IEnumerable<T> GetAll(int id){}
public virtual void Add (T entity){}
public void Delete(T entity){}
}
public abstract class BaseRepositoryWithUpdate<T> :
BaseRepositorySimple<T> where T : IEntity
{
public virtual void Update(T entity){}
}
Questa soluzione non scala bene. Diciamo che ho diverse entità che hanno un metodo comune: archivio virtuale pubblico vuoto (entità T) {}
ma alcune Entità che possono essere archiviate possono anche essere aggiornate mentre altre no. Quindi la mia soluzione di ereditarietà si rompe, dovrei creare due nuove classi base per affrontare questo scenario.
strategia compoisition
Ho esplorato il modello Compositon, ma questo sembra lasciare un sacco di codice refusi:
public class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity>
{
private Archiver<MyEntity> _archiveWrapper;
private GetByIDRetriever<MyEntity> _getByIDWrapper;
public MyEntityRepository()
{
//initialize wrappers (or pull them in
//using Constructor Injection and DI)
}
public MyEntity GetByID(int id)
{
return _getByIDWrapper(id).GetByID(id);
}
public void Archive(MyEntity entity)
{
_archiveWrapper.Archive(entity)'
}
}
Il MyEntityRepository è ora caricato con codice standard. C'è uno strumento/modello che posso usare per generare automaticamente questo?
Se potessi girare la MyEntityRepository in qualcosa di simile, penso che sarebbe di gran lunga l'ideale:
[Implement(Interface=typeof(IGetByID<MyEntity>),
Using = GetByIDRetriever<MyEntity>)]
[Implement(Interface=typeof(IArchive<MyEntity>),
Using = Archiver<MyEntity>)
public class MyEntityRepository
{
public MyEntityRepository()
{
//initialize wrappers (or pull them in
//using Constructor Injection and DI)
}
}
Aspect Oriented Programming
Ho guardato in utilizzando un framework AOP per questo, in particolare PostSharp e il loro Composition Aspect, che sembra che dovrebbe fare il trucco, ma al fine di utilizzare un repository dovrò chiamare Post.Cast <>(), che aggiunge un odore molto strano al codice. Qualcuno sa se c'è un modo migliore per usare AOP per aiutare a sbarazzarsi del codice boilerplate del compositor?
Custom Code Generator
Se tutto il resto fallisce, suppongo che potrei lavorare alla creazione di una spina di Visual Studio generatore di codice personalizzato in che potrebbe generare il codice piastra della caldaia in un file di codice parziale. C'è già uno strumento là fuori che farebbe questo?
[Implement(Interface=typeof(IGetByID<MyEntity>),
Using = GetByIDRetriever<MyEntity>)]
[Implement(Interface=typeof(IArchive<MyEntity>),
Using = Archiver<MyEntity>)
public partial class MyEntityRepository
{
public MyEntityRepository()
{
//initialize wrappers (or pull them in
//using Constructor Injection and DI)
}
}
//Generated Class file
public partial class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity>
{
private Archiver<MyEntity> _archiveWrapper;
private GetByIDRetriever<MyEntity> _getByIDWrapper;
public MyEntity GetByID(int id)
{
return _getByIDWrapper(id).GetByID(id);
}
public void Archive(MyEntity entity)
{
_archiveWrapper.Archive(entity)'
}
}
Metodi di estensione
dimenticato di aggiungere questo quando ho scritto inizialmente la questione (mi dispiace).Ho anche provato a sperimentare con metodi di estensione:
public static class GetByIDExtenions
{
public T GetByID<T>(this IGetByID<T> repository, int id){ }
}
Tuttavia, questo ha due problemi, a) che avrei dovuto ricordare lo spazio dei nomi della classe metodi di estensione e aggiungerlo ovunque e b) i metodi di estensione non può soddisfare le dipendenze di interfaccia:
public interface IMyEntityRepository : IGetByID<MyEntity>{}
public class MyEntityRepository : IMyEntityRepository{}
Aggiornamento: sarebbe T4 Templates essere una possibile soluzione?
Cerca Codesmith per la generazione del codice, se non lo hai già fatto, consente modelli personalizzati e regole molto granulari che puoi definire per generare codice C#. Sto pensando ad una soluzione che includa attributi personalizzati e un enumerato Flags, ma non sono sicuro se sia quello che vuoi. –
Grazie a @DavidKhaykin, guardando ora CodeSmith, sembra promettente. Non sai di un blog specifico o di un tutorial che risolva questo particolare problema, vero? –
Purtroppo non sono a conoscenza di nulla per questo specifico caso senza fare qualche ricerca. –