Контекст/вопрос
Я работал над многочисленными .NET-проектами, которые требовались для сохранения данных и обычно заканчивались использованием Repository pattern, Кто-нибудь знает о хорошей стратегии для удаления большого кода шаблонов без ущерба для масштабирования базы кода?
Стратегия наследования
Так как большая часть кода репозитория является плитой котла, и ее необходимо повторить, я обычно создаю базовый класс для покрытия основ, таких как обработка исключений, регистрация и поддержка транзакций, а также несколько базовых методов CRUD:
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){}
}
Итак, это хорошо работает, когда у меня есть простой домен, я могу быстро создать класс DRY repository для каждого объекта. Однако это начинает разрушаться, когда домен становится более сложным. Допустим, добавлен новый объект, который не позволяет обновлять. Я могу разбить базовые классы и перенести метод Update в другой класс:
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){}
}
Это решение плохо масштабируется. Скажем, у меня есть несколько Сущностей, которые имеют общий метод: public virtual void Archive (T entity) {}
но некоторые объекты, которые могут быть архивированы, также могут быть обновлены, а другие не могут. Поэтому решение My Inheritance ломается, мне нужно создать два новых базовых класса для решения этого сценария.
Стратегия Compoisition
Я исследовал шаблон Compositon, но это, похоже, оставляет много кода плиты котла:
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)'
}
}
Теперь MyEntityRepository загружается с шаблоном. Есть ли инструмент/шаблон, который я могу использовать для автоматического создания этого?
Если бы я мог превратить MyEntityRepository во что-то подобное, я думаю, что это было бы идеальным:
[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)
}
}
Аспектно-ориентированное программирование
Я изучил использование рамки AOP для этого, в частности PostSharp и их "Аспект композиции" , который выглядит так, будто он должен делать трюк, но для использования репозитория мне придется вызвать Post.Cast < > (), что добавляет очень странный запах к код. Кто-нибудь знает, есть ли лучший способ использовать АОП, чтобы избавиться от шаблона шаблона композитора?
Генератор пользовательских кодов
Если все остальное не работает, я полагаю, что я мог бы работать над созданием подключаемого модуля Visual Studio Generator Visual Studio, который мог бы генерировать код плиты котла в файл частичного кода. Есть ли там инструмент, который бы сделал это?
[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)'
}
}
Методы расширения
Забыл добавить это, когда я изначально написал вопрос (извините). Я также попытался экспериментировать с методами расширения:
public static class GetByIDExtenions
{
public T GetByID<T>(this IGetByID<T> repository, int id){ }
}
Однако у этого есть две проблемы: a) мне нужно будет запомнить пространство имен класса методов расширения и добавить его повсюду, а б) методы расширения не могут удовлетворять зависимостям интерфейса:
public interface IMyEntityRepository : IGetByID<MyEntity>{}
public class MyEntityRepository : IMyEntityRepository{}
Обновление: возможно ли T4 Templates?