EF Core без дополнительного шаблона репозитория - PullRequest
0 голосов
/ 08 июля 2020

Кажется, есть определенное движение, выступающее за то, что при использовании EF Core мы должны избегать создания шаблона репозитория и единицы работы, потому что EF Core уже реализует эти два, и мы можем использовать эту неявную реализацию. Это было бы здорово, потому что реализация этих шаблонов не всегда так проста, как может показаться. Итак, вот в чем проблема. При реализации репозитория способом 'classi c' у нас есть место для размещения кода, который строит наши объекты домена. Позвольте мне объяснить на примере; у нас есть сущности Invoice и InvoiceRow. В каждом счете-фактуре есть много строк InvoiceRows. Я включил только свойство навигации для краткости.

public class Invoice
{
    public int Id { get; set; }
    public DateTime Date { get; set; }
    public decimal Total { get; set; }
    public List<InvoiceRow> InvoiceRows { get; }
}

public class InvoiceRow
{
    public int Id { get; set; }
    public Invoice Invoice { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal RowPrice { get; set; }
}

Теперь мой бизнес-объект - это счет-фактура с его строками, и это должен быть единственный способ манипулировать счетами. При использовании «явного» репозитория мы должны сделать что-то вроде:

public class InvoicesRepo
{
    public AppDbContext AppDbContext { get; private set; }

    public Invoice Find(int id)
    {
        return

        AppDbContext.Invoices.Where(invoice => invoice.Id == id)
                    .Include(nameof(InvoiceRow))
                    .First();
    }
}

Это ограничивает доступ к счету-фактуре методом [InvoicesRepo]. Найти (id), который создает счет так, как ожидается код домена logi c.

Можно ли добиться этого с помощью простого EF Core? Может быть, работаешь с видимостью DbSets и / или дополнительных функций, о которых я не знаю? Поскольку кажется, что это фундаментальная функциональность полноценного репозитория, если это недостижимо, разве я разрушил главный аргумент экспертов, выступающих за отказ от (дополнительного) репозитория при использовании EF Core?

Ответы [ 2 ]

1 голос
/ 09 июля 2020

Можно ли этого добиться с помощью EF Core без оболочки? Возможно, вы работаете с видимостью DbSets и / или дополнительных функций, о которых я не знаю?

Конечно, принятие того, что DbContext - это ваш репозиторий, не означает, что вы не можете принимать проектные решения, и вы должны использовать дизайн DbContext по умолчанию.

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

public Invoice FindInvoice(int id)
{
    this.Invoices.Where(invoice => invoice.Id == id)
                .Include(nameof(InvoiceRow))
                .First();
}

Так что для код, которому требуется стандартная форма Invoice с InvoiceRows, они вызывают этот метод. Но для кода, которому нужна нестандартная форма, они все равно могут получить доступ к методам DbSets или IQueryable и создать собственный запрос. :

public IQueryable<Invoice> Invoices => this.Invoices.Include(nameof(InvoiceRow));

Затем, чтобы получить счета-фактуры без InvoiceRows, потребитель должен либо добавить к нему настраиваемую проекцию, например,

  db.Invoices.Where(i => i.CustomerID == custId).Select(i => new InvoiceDTO(i)).ToList();

, либо получить доступ к DbSet

 var invoice = db.Set<Invoice>().Find(invoiceId);

И вы можете организовать методы в своем DbContext, реализовав различные интерфейсы.

0 голосов
/ 09 июля 2020

Хорошо, это всего лишь предварительный ответ, основанный на множестве чтений повсюду.

public class InvoiceQuery
{
    public AppDbContext AppDbContext { get; private set; }

    public int Id { get; set; }

    public Invoice Execute()
    {
        return AppDbContext
                .Invoices
                .Include(nameof(Invoice.InvoiceRows))
                .Where(invoice => invoice.Id == Id)
                .FirstOrDefault();
    }
}

Моя проблема заключалась в том, что это не сильно отличается от того, что у нас было бы в репозитории . Это с практической точки зрения; с теоретической точки зрения, это дает вам правильную точку зрения, в то время как репозиторий вводит в заблуждение. Причина, по которой это вводит в заблуждение, заключается в том, что между сущностями и действиями или запросами, которые вы можете выполнять в базе данных, нет ассоциации 1-1. Даже в этом случае Invoice - это не просто счет-фактура, а Invoice plus InvoiceRows. (Кстати, я думаю, что InvoiceQuery - хорошее имя (а не InvoiceWithRowsQuery), потому что с точки зрения бизнес-логики c счет-фактура - это полностью загруженный счет-фактура; счет-фактура с 0 строками - это пустой счет-фактура, а не частично загруженный счет.)

Таким образом, запрос фокусируется на том, что вы получаете, а не на сущности, с которой вы начинаете, потому что их может быть более одного. Это имя «запроса» является своего рода нелогичным, потому что можно сказать, что по мере продвижения к бизнес-логике c вы перестаете видеть такие вещи, как запросы, и начинаете видеть такие вещи, как параметры. И на самом деле у нас есть параметры, «запрос» - это всего лишь имя контейнера. Возможно, нам следует называть это запросом бизнес-объекта, но это будет слишком долго, но в этом смысл. Таким образом, этот объект запроса представляет собой простой контейнер для запроса EF; Я расскажу об этом позже. В этой реализации на основе классов, помимо того, что она прославляется как объект, нет никаких дополнительных функций. Возможно, вы ожидаете дополнительных функций, вместо этого у вас есть что-то меньшее. Отсутствует то, что они больше не связаны с сущностью. Это еще один противоречащий интуиции факт, который доказывает, что здесь мы что-то разбираем: надуманное представление сущности-репозитория.

Наличие такого простого объекта ставит вопрос. Почему не метод какого-нибудь родственного класса? Если мы поместим его в Entity, мы go вернемся к организации, подобной репозиторию, что кажется некорректным в основном потому, что отсутствующий 1-1 bla bla означает, что во многих случаях вы не можете сказать, с какой сущностью связан запрос. Но некоторые авторы используют своего рода «контейнерный класс», который на самом деле является не более чем контейнером, просто не имеющим права на объект (например, OrdersData). Если я вам нравлюсь, как уроки, у которых есть четкая цель, это вызывает у вас дрожь по спине. Мы начали говорить, что у нас есть решение лучше, чем репозиторий. Что у репозитория так много проблем. В итоге мы получаем класс, в названии которого есть «Данные». Как может лучшее решение быть таким неубедительным? Похоже, что альтернатива репозиторию - это просто куча вещей, а не стабильная и хорошо спроектированная, как ожидалось. С другой стороны, это всего лишь способ сбора запросов (те же запросы, которые были бы у вас в репозитории, очень тот же запрос, который у вас был бы в шаблоне запроса / команды ...) под имя, отличное от имени сущности. Да, похоже, все это сводится к выбору наименования. Сторонники модели репозитория и сторонники запрета на репозиторий яростно борются друг с другом. Затем вы смотрите на код, что он на самом деле делает. Код тот же, и мы говорим только об именах. То есть: если вы посмотрите, что репозитории делают в своих репозиториях, а нерепозитории делают в своих любых (не всегда ясных) классах, они делают то же самое. Но это всего лишь впечатление, я должен это проверить.

В модели репозитория наличие репозитория для объекта с его методами было обнадеживающим. Он предоставил своего рода основу для размещения всех методов. К сожалению, эти леса кажутся слишком ограничивающими, опять же из-за отношения не 1-1 между сущностями и запросами / командами. Тем не менее, мы должны сказать, что объекты root, такие как Invoice (в DDD разговоре Invoice - это root, InvoiceRows являются агрегированными), вполне подходят для организации в стиле репозитория. По этой причине в этом решении, основанном на запросах, плоскостность коллекции запросов не полностью удовлетворяет, и это еще одна причина, по которой я думаю, что хочу более подробно остановиться на этом топе c.

...