Написание вычисляемых членов с помощью Entity Framework Core - PullRequest
5 голосов
/ 06 января 2020

Мы используем Entity Framework Core 3 с базой данных SqlServer. Бизнес-программе необходимо создать много столбцов, которых нет в базе данных, из-за хранения, высокой стоимости запросов и т. Д. c. В настоящее время команда копирует весь слой базы данных и создает еще один слой, добавляя вычисляемые элементы в новые сущности. В настоящее время берется слой базы данных и применяется AutoMapper к новому слою. По какой-то причине это не похоже на оптимальный метод.

В этом примере нам требуются вычисляемые члены с именем

FullName => FirstName + LastName

AccountValue => Quantity * StockPrice

Entity Framework 3 больше не разрешает оценку на стороне клиента, https://devblogs.microsoft.com/dotnet/announcing-ef-core-3-0-and-ef-6-3-general-availability/ так любопытно, что такое стандартизированный способ для вычисляемых элементов в Entity Framework Core 3?

Читая эту статью, любопытно узнать, что актуально и что можно использовать? Или Entity Framework Core 3 предлагает новый синтаксис?

https://daveaglick.com/posts/computed-properties-and-entity-framework

1) Мы могли бы материализовать сущности. Кажется, все в порядке, однако это заставляет разработчика помнить, что при использовании ToList () возникали проблемы, из-за которых разработчики забывали, вызывая длинные запросы на сканирование базы данных или ошибки оценки на стороне клиента.

var result = ctx.Customers
  .ToList()
  .Select(c => new
  {
    FullName = c.FullName,
    AccountValue = c.AccountValue
  });

2) Создать расширение Queryable. Это только извлекает вычисляемые столбцы или вынуждает разработчиков создавать вычисляемые элементы в одном классе (нарушает идею единой ответственности SRP). Если нет альтернативной модификации, которая решает эту проблему. Это также приводит к проблемам в составной цепочке и возможным проблемам с производительностью, таким как опция 1.

public static IQueryable<CustomerData> SelectCustomerData(this IQueryable<Customer> customers) {   return customers.Select(c => new CustomerData   {
    FullName = c.FirstName + " " + c.LastName,
    AccountValue = c.Holdings.Sum(h => h.Quantity * h.Stock.Price)   }); }

3) Проекция выражений, не позволяет присваивать в Select без Linq Expression Project. Компания не допускает использование этого стороннего инструмента, если только он не создан Microsoft.

public readonly Expression<Func<Customer, decimal>> AccountValueExpression = c => c.Holdings.Sum(h => h.Quantity * h.Stock.Price);

Или Entity Framework Core 3 предлагает новый синтаксис?

Решение должно быть там, где (а) человек может извлечь некоторые или все существующие члены исходного DBEntity, (b) и некоторые или все новые члены,

Пример, необходимо FirstName (Existing) и AccountValue (New Member)

или FullName , FirstName, LastName, StockPrice,

Или Все, FirstName, LastName, FullName, Количество, StockPrice, AccountValue, et c, et c

Любое сочетание или совпадение от сущностей.

На самом деле при переходе с 2.2 на Core 3, однако в 2.2 отключена оценка клиента. Нельзя использовать сторонние инструменты, такие как Linq.Translations или DelegateCompiler, если они не созданы от поставщика Microsoft.

Предпочитают не использовать столбцы SqlServer Computed, поскольку мы полагаемся на команду DBA. Кроме того, есть более сложные вычисления.

Ответы [ 4 ]

5 голосов
/ 06 января 2020

Оценка на стороне клиента - зло, поэтому разработчики EF Core 3 приняли правильное решение, чтобы запретить это. Код, который может быть оценен на клиенте, часто приводит к раздражающим проблемам с производительностью. Поэтому я бы не рекомендовал вам использовать вычисляемые свойства в EF Core 2. *.

что является стандартным способом для вычисляемых элементов в Entity Framework Core

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

Для этой задачи вы можете использовать библиотеку AutoMapper . Он автоматически сопоставляет свойства с тем же именем. Другие свойства (вычисляемые свойства) могут быть сопоставлены с помощью пользовательского выражения.

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Customer, CustomerDto>()
        .ForMember(x => x.FullName, x => x.MapFrom(z => z.FirstName + " " + z.LastName))
        .ForMember(x => x.AccountValue, x => x.MapFrom(z => z.Quantity * z.StockPrice));
});
var mapper = config.CreateMapper();

Затем вы можете использовать ProjectTo метод расширения. ProjectTo внутренне вызовите Select, чтобы он не материализовал сущность. Следовательно, оператор Where анализируется в запросе SQL.

var customers = await context.Customers
    .ProjectTo<CustomerDto>(mapper.ConfigurationProvider)
    .Where(x => x.FullName == "full name" && x.AccountValue > 4)
    .ToListAsync();

Проектирование объектов часто является хорошей практикой. Он позволяет вам выбрать только несколько столбцов из БД и предлагает вам другие вещи, которые невозможны при возврате просто простых объектов (например, сортировка):

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Invoice, InvoiceDto>();
    cfg.CreateMap<Customer, CustomerDto>()
        .ForMember(x => x.Invoices, x => x.MapFrom(z => z.Invoices.OrderBy(x => x.Date)));
});
var mapper = config.CreateMapper();

// Customers with invoices sorted by date
var customers = await context.Customers
    .ProjectTo<CustomerDto>(mapper.ConfigurationProvider)
    .ToListAsync();

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

1 голос
/ 14 января 2020

Прежде всего, позвольте мне процитировать EF 3 уведомление о критических изменениях здесь:

Начиная с 3.0, EF Core допускает только выражения в проекции верхнего уровня ( последний вызов Select () в запросе) для оценки на клиенте. Когда выражения в любой другой части запроса не могут быть преобразованы в SQL или в параметр, возникает исключение.

Я только что успешно протестировал следующий запрос:

var list = context.Customers.Include(c => c.Stocks).Select(c => new
{
    FullName = c.FirstName + " " + c.LastName,
    TotalInvestment = c.Stocks.Sum(s => s.Price*s.Quantity)
});
list.ToList();
/*
      SELECT ([c].[FirstName] + N' ') + [c].[LastName] AS [FullName], (
          SELECT SUM([s].[Price] * [s].[Quantity])
          FROM [Stocks] AS [s]
          WHERE [c].[Id] = [s].[CustomerId]) AS [TotalInvestment]
      FROM [Customers] AS [c]
*/

Но давайте рассмотрим топи c немного дальше и скажем, что вы хотите запросить вашу таблицу по вычисляемому полю, не перенося всю оценку на клиентскую сторону.

var list = context.Customers.Include(c => c.Stocks)
                    .Where(c => string.Concat(string.Concat(c.FirstName, " "), c.LastName) == "John Doe") // notice how we have to do string.Concat twice. observe what hppens if you use the next line instead
                    //.Where(c => string.Concat(c.FirstName, " ", c.LastName) == "John Doe"); // this query will fail to evaluate on SQL and will throw an error, because EF's default concat translator implementation only caters for two parameters
                ;
list.ToList();
/* the following SQL has been generated
      SELECT [c].[Id], [c].[FirstName], [c].[LastName], [s].[Id], [s].[CustomerId], [s].[Price], [s].[Quantity]
      FROM [Customers] AS [c]
      LEFT JOIN [Stocks] AS [s] ON [c].[Id] = [s].[CustomerId]
      WHERE (([c].[FirstName] + N' ') + [c].[LastName]) = N'John Doe'
      ORDER BY [c].[Id], [s].[Id]
*/

Очевидно, EF3 поставляется с несколькими предварительно встроенными функциями, которые позволяют вам сделать это: см. IMethodCallTranslator реализации (например, StringMethodTranslator), чтобы получить немного больше информации о том, как он это делает (и о других доступных вам переводчиках из коробки).

Хорошо, что если ваш переводчик не реализован, я слышу, как вы спрашиваете. Это где вещи становятся немного более захватывающими. Я успешно сделал это для EF 2.2, как указано в этом SO-ответе (который я приглашаю вас проверить). К сожалению, код напрямую не переводится в EF3 1: 1, но я уверен, что тот же подход будет работать.

UPD : см. my github repo для Po C пользовательских функций БД, работающих с EF Core 3.1

0 голосов
/ 08 января 2020

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

    public string FullName => FirstName + LastName;

    entity.Property(e => e.FullName).HasComputedColumnSql("FirstName + LastName");

Затем вы можете тривиально отфильтровать, упорядочить, проецировать и c эти свойства.

0 голосов
/ 06 января 2020

Я не пробовал это, но это всего лишь мысль - как насчет создания пользовательского Extension метода DbFunctions?

Я использовал EF.Functions.Like метод, который является реализацией SQL КАК операция. В реляционных базах данных это обычно напрямую переводится в SQL.

Проверьте эти ссылки -

...