Как универсально создавать репозитории, которые наследуются от универсального типа? - PullRequest
0 голосов
/ 21 октября 2018

В настоящее время я пытаюсь реализовать шаблон репозитория поверх моего DbContext.Дело в том, что я в конечном итоге сталкиваюсь с ситуацией, когда мне приходится вставлять несколько репозиториев в конструктор UnitOfWork, например:

public class UnitOfWork
{
    private DbContext _context;
    ICustomerRepository Customers { get; private set; }
    IEmployeeRepository Employees { get; private set; } 
    public UnitOfWork(DbContext context, ICustomerRepository cust, IEmployeeRepository emp)
    {
        _context = context;
        Customers = cust;
        Employees = emp;
    }    
}

Однако, поскольку все они должны использовать один и тот же DbContextЯ не вижу возможности их внедрения.

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

Чтобы дать вам более глубокое понимание, так выглядит код :

public interface IRepository<TEntity> where TEntity:class
{
    TEntity Get(int id);
    IEnumerable<TEntity> GetAll();
}

public interface ICustomerRepository : IRepository<Customer>
{
    IEnumerable<Customer> GetSeniorCustomers();
}

public class CustomerRepository : ICustomerRepository
{
    private readonly DbContext _context;
    public CustomerRepository(DbContext context) : base(context)
    {
        _context = context;
    }
// ... implementation of ICustomerRepo here
}

Вот текущее положение вещей:

И что я хотел бы сделатьэто:

public UnitOfWork(DbContext context, RepositoryFactory fac)
{
     _context = context;
     Customers = fac.Create(context, RepoType.Customer);
     Employees = fac.Create(context, RepoType.Employee);
}  

Я знаю, что это на самом деле не дает мне никакой дополнительной гибкости, но, на мой взгляд, делает код немного менее неуклюжим.

Но, как я упоминал ранееЯ не могу придумать правильный тип возвращаемого значения дляМетод Create ().

Итак, у меня возникла идея создать несколько методов внутри класса RepositoryFactory вместо одного параметризованного, например:

public class RepositoryFactory
{
    public ICustomerRepository CreateCustomerRepo(DbContext context){/*...*/}
    public IEmployeeRepository CreateEmployeeRepo(DbContext context){/*...*/} 
}

Итак, вопросыявляются:

  1. Может ли то, что я делаю, даже называться фабричным методом?
  2. Если нет, то является ли это хотя бы верным решением?Если нет, то как я могу добиться того же самого более чистым способом?

К достичь того же самого Я имею в виду реализовать способ создания этих репозиториевв управляемой, краткой форме.

Спасибо за всю помощь заранее.

Ответы [ 2 ]

0 голосов
/ 23 октября 2018

Во-первых, будьте ясны в отношении своей цели.Шаблон репозитория имеет (как минимум) 3 основных причины существования:

1) Абстрагирование слоя данных.Что касается EF, то если это ваша цель, то шаблон репозитория больше не выгоден.Попытка абстрагировать приложения от Entity Framework - гораздо больше проблем, чем оно того стоит.В конечном итоге вы получите либо / или урезанный DAL, в котором нет ничего удивительного в том, что EF может предоставить, либо неэффективный / медленный, либо очень сложный набор методов репозитория с выражениями и другими неприятностями в качестве параметров.Попытки абстрагировать ваше приложение от EF (на случай, если вы захотите, например, перейти на другой ORM) бессмысленно.Примите EF так же, как вы приняли бы тот факт, что вы пишете свое заявление в .Net.Чтобы абстрагировать EF до такой степени, что его можно заменить, вы также можете не использовать его, потому что вы не увидите никаких преимуществ, которые EF может на самом деле предоставить.

2) Для упрощения тестирования бизнес-логики.IMO Это все еще допустимый аргумент для репозиториев с Entity Framework.Да, EF DbContexts может быть высмеянным, но это все еще грязный бизнес.Пересмешивать репозитории не сложнее, чем любая другая зависимость.

3) служить привратником домена.Шаблоны, такие как DDD, пытаются заблокировать действия с данными в объектах и ​​сервисах домена.Шаблон репозитория может помочь с этим при использовании EF, чтобы помочь содержать методы, которые отвечают за манипулирование доменом.Для чистого DDD я бы не рекомендовал их, хотя я бы не рекомендовал использовать Entities в качестве объектов домена DDD.Я использую шаблон репозитория для управления аспектами CRUD в аспектах CR и D. и полагаюсь на модели представлений для инкапсуляции логики домена вокруг U.

Шаблон репозитория, который я нашел наиболее полезным для обслуживания точек 2& 3 состоит в том, чтобы покончить с очень распространенной концепцией универсальных репозиториев, и скорее относиться к репозиторию более в соответствии с тем, как вы относитесь к контроллеру в MVC.За исключением случаев, когда между View и Model, между Model и Data.Репозиторий - это класс, который обслуживает контроллер (в MVC), поскольку он отвечает за создание, чтение (на уровне ядра) и удаление сущностей.Этот шаблон очень хорошо работает в сочетании с единицей работы.(https://github.com/mehdime/DbContextScope - это реализация, которую я принимаю.)

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

В Считывающих сущностях он предоставляет базовую ссылку надалее запрашивать объекты, предоставляя IQueryable<TEntity>, применяя правила базового уровня, такие как .Where(x => x.IsActive) для сценариев мягкого удаления, или фильтры для аутентификации / авторизации / аренды на основе зависимостей, которые, например, могут выявить текущего пользователя.Предоставляя IQueryable, вы сохраняете реализацию репозитория простой и предоставляете потребителю (контроллеру) контроль над тем, как используются данные.Это может использовать отложенное выполнение для:

  • Выбор только данных, необходимых для моделей представления.
  • Выполнение подсчетов и существующих проверок.(.Any())
  • Настроить логику фильтрации по структуре объекта для конкретного варианта использования.
  • Выполнить разбиение на страницы.
  • Получить столько (.ToList (), .Take()) или как можно меньше (.SingleOrDefault (), .FirstOrDefault ()) данных.

Методы чтения чрезвычайно легко смоделировать, и поэтому объем реализации репозитория достаточно мал.Потребители должны знать, что они имеют дело с сущностями, и иметь дело с нюансами EF и это прокси, но потребитель является хранителем отдела работы (продолжительности жизни DbContext) так скрывая этот факт от него довольно спорного вопроса,Передача сложных выражений запросов в методы репозитория в качестве параметров оставляет потребителей в равной степени ответственными за знание нюансов EF.Выражение, которое вызывает закрытый метод, передаваемый в универсальный репозиторий для перехода в предложение Where, будет так же быстро разрушать вещи.Если вы спуститесь по этому маршруту, выполните пункт № 1 выше, не абстрагируйте EF от вашего приложения.

При удалении объекта он обеспечивает правильное управление объектами и их ассоциациями, будь то жесткое или мягкое удаление.

Я избегаю универсальных репозиториев, потому что так же, как контроллер (и представление) будет иметь дело с любым количеством связанных моделей представления домена, это означает, что им потребуется иметь дело с рядом связанных объектов данных.Операции против какой-либо одной сущности будут неизменно связаны с операциями против других иждивенцев.В универсальных репозиториях это разделение означает: а) постоянно растущее число зависимостей и б) универсальные методы, которые выполняют тривиальную дрянь, и много пользовательского кода для обработки значимых вещей, или сложный код, чтобы попытаться упростить его в общем (базовом) виде,Наличие одного репозитория на контроллер и, возможно, некоторых действительно общих общих репозиториев для общих объектов.(например, поиск). Мои репозитории специально разработаны для обслуживания одной области приложения и имеют только одну причину для изменения.Конечно, может быть 2 или более экранов, которые требуют одинакового поведения от репозитория, но по мере развития этих аспектов приложения или службы их репозитории могут при необходимости обновляться / оптимизироваться без побочных эффектов.SRP и KISS легко превосходят DNRY.

Общие классы в целом имеют свое применение, но почти в любом случае, когда я вижу, что разработчики пишут их, я бы сказал, что это преждевременная оптимизация.Начните с неуниверсальных реализаций, затем, по мере развития продукта, оптимизируйте обобщенные в код, а не пытайтесь проектировать архитектуру вокруг них.Результатом почти всегда является осознание того, что вам необходимо де-оптимизировать их или получить «умный» подход к обходу обнаруженных ограничений, когда шаблоны препятствуют развитию.

В любом случае, есть некоторая пища для размышлений, отличная от «репозиториев».требуется с EF ":)

0 голосов
/ 22 октября 2018

Ваше решение «множественные методы внутри класса RepositoryFactory» весьма неплохо, если вас не волнует принцип Open / Close.Если это соответствует вашим потребностям, вы можете пойти с этим.

Может ли то, что я делаю, даже называться Фабричным методом?

Да;это своего рода фабричные методы.В любом случае, не волнуйтесь слишком долго, пока оно удовлетворяет вашим требованиям, не создавая новую проблему.

Если нет, то является ли это хотя бы верным решением?

Как сказановыше, если это соответствует вашим потребностям;да, это действительно.

Если нет, то как я могу добиться того же самого более чистым способом?

Другой альтернативой является заводской метод, как показано ниже:

public T CreateRepository<T>() where T : IRepositoryId
{
    IRepositoryId repository = null;
    if(typeof(T) == typeof(ICustomerRepository))
        repository = new CustomerRepository(context);
    ......
    ......
    else
        throw new XyzException("Repository type is not handled.");
    return (T)repository;
}

public interface IRepositoryId
{
    Guid RepositoryId { get; }
}

Ваш существующий интерфейс IRepository является общим.Это создает проблемы при реализации вышеуказанного метода с этим интерфейсом.Итак, просто создайте другой интерфейс, как показано выше.Получите каждый репозиторий из этого интерфейса.

...