EF Core 3.1 Intermediate Projection VS Iterating Result to Set Additional Property - PullRequest
0 голосов
/ 07 мая 2020

Скажем, у меня есть модель сущности ..

public class MyEntity
{
  public DateTime TransactionDate {get; set;}
  public decimal TotalSales {get; set;}
  public long TotalPurchases {get; set;}
}

, и я довольно легко заполняю список этой сущности из базы данных SQLServer для диапазона дат транзакции.

var results = await _dbContext.StoresDailyTransactions
    .Where(t => t.TransactionDate > = rangeStartDate && t.TransactionDate <= rangeEndDate)
    .GroupBy(o => o.TransactionDate)
    .Select(g => new MyEntity
    {
        TransactionDate = g.Key,
        TotalSales = g.Sum(c => c.Sales),
        TotalPurchases = g.Sum(c => c.Purchases),
    })
    .DefaultIfEmpty()
    .ToListAsync();

Теперь необходимо добавить AverageSale к сущности ...

public class MyEntity
{
  public DateTime TransactionDate {get; set;}
  public decimal TotalSales {get; set;}
  public long TotalPurchases {get; set;}
  public decimal AverageSale {get; set;}
}

Это достаточно просто, потому что у меня есть значения, уже выбранные в моем запросе, просто нужно вычислить среднее и присвоить его новое свойство AverageSale ....

  AverageSale = TotalSales / TotalPurchases

Я рассматривал возможность сделать это двумя разными способами.

1) Просто повторите мой существующий список результатов, полученных ранее, и установите новое свойство перед возвратом результаты.

  foreach(MyEntity currentEntity in results)
  {
    currentEntity.AverageSales = currentEntity.TotalSales / currentEntity.TotalPurchases;
  }

2) Сделайте все в операторе linq, выбрав промежуточную анонимную проекцию, а затем окончательную проекцию MyEntity с новым AverageSale.

var results = await _dbContext.StoresDailyTransactions
    .Where(t => t.TransactionDate > = rangeStartDate && t.TransactionDate <= rangeEndDate)
    .GroupBy(o => o.TransactionDate)
    .Select(g => new 
    {
        TransactionDate = g.Key,
        TotalSales = g.Sum(c => c.Sales),
        TotalPurchases = g.Sum(c => c.Purchases),
    })
    .Select(a => new
    {
        TransactionDate = a.TransactionDate,
        TotalSales = a.TotalSales,
        TotalPurchases = a.TotalPurchases,
        AverageSale = a.TotalSales / a.TotalPurchases,
    })
    .DefaultIfEmpty()
    .ToListAsync();

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

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

Есть ли что-нибудь еще, что делает один из них объективно лучше, например, производительность et c или может быть, даже лучший способ, который я не рассматривал?

1 Ответ

2 голосов
/ 07 мая 2020
public class MyEntity
{
    public DateTime TransactionDate { get; set; }
    public decimal TotalSales { get; set; }
    public long TotalPurchases { get; set; }
    public decimal AverageSale => this.TotalSales / this.TotalPurchases;
}

Ваш запрос останется прежним:

var results = await _dbContext.StoresDailyTransactions
                              .Where(t => t.TransactionDate >= rangeStartDate 
                                     && t.TransactionDate <= rangeEndDate)
                              .GroupBy(o => o.TransactionDate)
                              .Select(g => new MyEntity
                              {
                                  TransactionDate = g.Key,
                                  TotalSales = g.Sum(c => c.Sales),
                                  TotalPurchases = g.Sum(c => c.Purchases),
                              })
                              .DefaultIfEmpty()
                              .ToListAsync();
...