2010-11-14 2 views
3

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

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

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 должны быть аудит. Как мне продолжить?

+0

действительно ли абстрактный базовый класс обеспечивает любую реализацию? –

+0

@ Russ Cam: Нет, я вставил его как есть. Должен ли я удалить его? Я использую фабрику репозитория для создания репозиториев с Ninject, вводящих соответствующий тип базы данных (разные физические базы данных). Эти репозитории затем вводятся в мои контроллеры MVC 2. – John

+2

Я не думаю, что абстрактный класс в текущей форме дает вам что-то дополнительное по интерфейсу. Я думаю, вы могли бы удалить абстрактный класс. –

ответ

5

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

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, некоторые - нет. Это становится все хуже с каждой новой функцией, которая может или не может применяться, часто в конечном итоге превращаясь в флаги конфигурации и беспорядочное внутреннее ветвление.

+0

Будет ли EFRepository затем наследовать от Аудитора ? Как я могу скрыть данные аудита из EFRepository, и поведение аудита происходит «за кадром» с помощью шаблона Decorator, который вы показывали? – John

+0

@Smith, вы передадите экземпляр EFRepository в Аудитор. Затем вы будете использовать Аудитора для всего вашего доступа. Вся идея декоратора заключается в том, что он не знает, что он украшает, кроме того, что он следует за каким-то контрактом. Он просто обертывает существующую логику дополнительной функцией. –

2

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

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

+0

Захваченные события - хорошее решение этой проблемы. Тем не менее, следует проявлять некоторую осторожность, когда вы раскрываете события в слое данных. Вещи могут быстро выйти из-под контроля, когда один вызов Update в слое данных приводит к целой цепочке сложных событий, непреднамеренно просачиваясь обратно через границы Remoting, поскольку модуль Jim получает события, выпущенные модулем Тома, который получил обновление от Алисы, потому что она решила предпринять действия по созданию. –

+0

Да, согласны, события - правильная механика, чтобы использовать, вероятно, но, как их использовать, по-прежнему остается вопросом дизайна, который требует некоторой тщательной мысли, особенно когда дело доходит до побочных эффектов. – Homde

+0

Я думаю, что ключом к безопасному использованию событий в слое данных является чтобы убедиться, что они изолированы от уровня данных (т.е. нет видимости общественности и никаких публичных побочных эффектов). Это позволяет вам кодировать в реактивном стиле (возможность даже с использованием Rx), но значительно уменьшает вероятность кошмаров состояния гонки. –

2

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

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

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

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

Из Вашего вопроса, то не совсем ясно, как «проверяемым репозиторий» должен вести себя, но если это просто обычный 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); 
    } 
} 
+0

В целом рекомендация (из руководящих принципов разработки рамок) заключается в использовании абстрактных классов, а не интерфейсов для наследования. Интерфейсы предоставят вам больше свободы, но если люди будут зависеть от ваших интерфейсов, вы не сможете добавлять методы позже в свой интерфейс, не нарушая их реализации. Также вы можете захотеть использовать некоторые методы по умолчанию в своем абстрактном классе.Интерфейсы более уместны чаще всего, когда у вас есть только небольшое общее подмножество методов, которые применимы к широкому спектру разных разных классов. – Homde

+0

@MattiasK: это правда, хотя есть еще способы обойти это: вы создаете другой интерфейс и пусть он содержит старый, а также некоторые новые методы. Таким образом, вы можете добавлять методы только к тем классам, которые нуждаются в функциональности. Кроме того, внедрение методов расширения также немного изменило ситуацию в пользу интерфейсов. – Groo

+0

@Groo: я удалил репозиторий за ваш совет и совет Руса. Спасибо. Я все еще читаю все предложения в этой теме сейчас! – John

3

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

sealed class EFRepository<TEntity> : Repository<TEntity> 
    where TEntity : EntityObject 
{ 
    ObjectContext _context; 
    ObjectSet<TEntity> _entitySet; 
    IAuditor _auditor; 

    public EFRepository(ObjectContext context) : this(context, null) 
    { 
    } 
    public EFRepository(ObjectContext context, IAuditor auditor) 
    { 
     _context = context; 
     _entitySet = _context.CreateObjectSet<TEntity>(); 
     _auditor = auditor; 
    } 
    public override void Create(TEntity entity) 
    { 
     _entitySet.AddObject(entity); 
     _context.SaveChanges(); 

     if (_auditor != null) 
     { 
      // audit 
     } 
    } 

    // etc. 
} 
+0

Это предпочтительнее создавать новые производные составные типы, но может стать несостоятельным по мере увеличения числа функций надстройки. –

Смежные вопросы