Вопрос о дизайне репозитория C # - PullRequest
3 голосов
/ 15 ноября 2010

Я пишу слой данных EF4 для веб-приложения MVC 2, и мне нужны предложения по выбору наследования или абстрактных базовых классов. Мой репозиторий хорошо работал, следуя структуре «универсального репо», но теперь я хочу добавить функцию «Аудит», которая записывает каждый раз, когда выполняется операция CRUD.

Это контракт, который я использовал до сих пор:

public interface IRepository<T>
{
    void Create(T entity);
    void Update(T entity);
    void Delete(Func<T, bool> predicate);
    T Get(Func<T, bool> predicate);
    IQueryable<T> Query();
}

Мой репо. реализация выглядит так:

sealed class EFRepository<TEntity> : IRepository<TEntity>
    where TEntity : EntityObject
{
    ObjectContext _context;
    ObjectSet<TEntity> _entitySet;

    public EFRepository(ObjectContext context)
    {
        _context = context;
        _entitySet = _context.CreateObjectSet<TEntity>();
    }

    public void Create(TEntity entity)
    {
        _entitySet.AddObject(entity);
        _context.SaveChanges();
    }

    public void Update(TEntity entity)
    {
        _entitySet.UpdateObject(entity);
        _context.SaveChanges();
    }

    public void Delete(Func<TEntity, bool> predicate)
    {
        TEntity entity = _entitySet.Single(predicate);
        _entitySet.DeleteObject(entity);
        _context.SaveChanges();
    }

    public TEntity Get(Func<TEntity, bool> predicate)
    {
        return _entitySet.SingleOrDefault(predicate);
    }

    public IQueryable<TEntity> Query()
    {
        return _entitySet;
    }
}

Я хочу создать концепцию AuditableRepository<T>. Должен ли я создать его так:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : AuditableRepository<T>

или лучше иметь его так:

interface IAuditable<T>
interface IRepository<T>
EFRepository<T> : IRepository<T>, IAuditable<T>

или даже:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : IRepository<T>
AuditableEFRepository<T> : AuditableRepository<T>

Не все мои EFRepositories нужно будет проверять. Как мне поступить?

Ответы [ 4 ]

5 голосов
/ 15 ноября 2010

Вот еще одна возможность (использование объекта Decorator для добавления дополнительных функций в существующий репозиторий):

public sealed class Auditor<T> : IRepository<T>
{
    private readonly IRepository<T> _repository;

    public Auditor(IRepository<T> repository)
    {
        _repository = repository;    
    }

    public void Create(T entity)
    {
        //Auditing here...
        _repository.Create(entity);
    }

    //And so on for other methods...
}

Преимущество использования декоратора для добавления дополнительных функций заключается в том, что он позволяет избежать комбинаторного взрыва, который вы начали видеть, когда рассматривали некоторые репозитории с аудитом, некоторые без, некоторые с использованием EF, некоторые нет. Это становится все хуже с каждой новой функцией, которая может или не может применяться, часто в конечном итоге превращается в флаги конфигурации и грязное внутреннее ветвление.

3 голосов
/ 15 ноября 2010

Будет ли иметь значение, является ли хранилище проверяемым или нет?То есть вам нужно знать, является ли репозиторий IAuditableRepository или просто IRepository?Если нет, вы можете использовать DI и добавить конструктор, который принимает IAuditor.Тогда в ваших методах репозитория, если доступен IAuditor, вы можете использовать его.

2 голосов
/ 15 ноября 2010

Прежде всего, если ваш Repository<T> не имеет какой-либо общей функциональности (у вас есть только абстрактные методы и нет реализации), первое, что я хотел бы сделать, это избавиться от него.Лучше всего, чтобы все другие части вашей программы обращались к хранилищу только через интерфейс IRepository<T>, что позже предоставит вам большую свободу.

Во-вторых, наличие класса EFRepository означает, что вы хотите выйтивозможность переключиться на другой ORM один день.Мое мнение - не делай этого.Если вы выбрали EF, придерживайтесь EF.Если вы действительно думаете, что это вариант, хотя бы создайте отдельную сборку с пространством имен MyApp.EntityORM или любым другим, поместите туда все свои классы репозитория и избавьтесь от префикса EF.Затем, если это когда-нибудь случится, возможно, вы когда-нибудь сможете загрузить нужный ORM с помощью внедрения зависимостей, не меняя остальной код.

Из моего опыта: между ORM могут быть тонкие различия, которые мешают вамот их взаимозаменяемости по-настоящему прозрачным способом, и есть небольшой шанс, что вам когда-нибудь понадобится изменить свой ORM для одного проекта.

Во-вторых, я также предпочитаю оборачивать базовую функциональность базового репозитория в другом классе.(образец декоратора, как уже сказал Дэн Брайант), в отличие от переопределения уже существующего класса.Причина в том, что переопределение часто приводит к необходимости иметь дело с внутренними компонентами базового класса, и это может иногда становиться немного запутанным.

Из вашего вопроса не совсем ясно, как должен вести себя «контролируемый репозиторий», но если это просто IRepository<T> (он проверяет все методы, реализованные в IRepository<T>), то вам не нужен отдельный интерфейс:

interface IRepository<T>
class Repository<T> : IRepository<T>
class AuditableRepository<T> : IRepository<T>

Добавляя к тому, что сказал Дэн, выможет иметь фабричный метод, который создаст соответствующую оболочку:

class AuditableRepository<T> : IRepository<T>
{
     public static IRepository<T> CreateFrom(IRepository<T> baseRepo)
     { 
          // wrap base repo in an auditable repository
          // and return it
          return new AuditableRepository<T>(baseRepo);
     }
}
2 голосов
/ 15 ноября 2010

Ни то, ни другое не кажется идеальным решением, так как Дэн говорит, что вы можете столкнуться с проблемой с различными комбинациями интерфейсов.Функции аудита не звучат так, как будто они действительно должны быть частью класса.Я думаю, что вместо того, чтобы придерживаться подхода Дэна, я бы объявил и запустил события в целом в хранилище.Таким образом, вы можете подключить к нему много разных вещей, гораздо более гибких.Поэтому объявляйте такие события, как «Создано», «Удалено» и т. Д.

. В будущем он также предоставляет некоторые действительно приятные функциональные возможности, например, реактивные расширения.

...