Подтвердить что ты не робот

Entity Framework Выберите новый POCO без .ToList() сначала

Я создаю приложение со слоем сервиса (веб-сайт WCF) и клиентом Silverlight 4. Услуги RIA не являются опцией, поэтому мы создаем промежуточные классы для перехода туда и обратно. Для этого вопроса позвольте предположить, что я перехожу туда и обратно Вкусные Food Объекты.

public class FoodData
{
  public int Id { get; set; }
  public string Name { get; set; }
  public Tastyness TastyLevel { get; set; }
}

Модель EF - это, по сути, тот же класс, таблица с тремя основными полями (Tastyness - это int, которая соответствует нашей Enum Tastyness).

Я часто использую этот тип операторов при выполнении запросов Entity Framework:

public List<FoodData> GetDeliciousFoods()
{
  var deliciousFoods = entities.Foods
                               .Where(f => f.Tastyness == (int)Tastyness.Delicious)
                               .ToList()  // Necessary? And if so, best performance with List, Array, other?
                               .Select(dFood => dFood.ToFoodData())
                               .ToList();

  return deliciousFoods;
}

Без вызова .ToList() я получаю исключение из того, что LINQ не может перевести пользовательский метод в эквивалент запроса, который я понимаю.

Мой вопрос о вызове .ToList() перед .Select(...) с пользовательским расширением для преобразования нашего объекта в версию POCO Продовольственный объект.

Есть ли лучший шаблон, который можно сделать здесь, или, может быть, даже лучшая альтернатива .ToList(), которая может быть более эффективной, поскольку на самом деле мне не нужна функциональность результата List <.. > .

4b9b3361

Ответ 1

Проблема с использованием ToList или AsEnumerable заключается в том, что вы материализуете весь объект и оплачиваете стоимость исправления. Если вы хотите иметь наилучший SQL, который возвращает только нужные поля, вы должны проектировать напрямую, а не использовать .ToFoodData():

var deliciousFoods = entities.Foods
                             .Where(f => f.Tastyness == (int)Tastyness.Delicious)
                             .Select(dFood => new FoodData
                                  {
                                      Id = dFood.Id,
                                      Name = dFood.Name,
                                      TastyLevel = (Tastyness)dFood.Tastyness
                                  });

Приведение в перечисление может быть проблемой. Если да, пройдите анонимный тип:

var deliciousFoods = entities.Foods
                             .Where(f => f.Tastyness == (int)Tastyness.Delicious)
                             .Select(dFood => new FoodData
                                  {
                                      Id = dFood.Id,
                                      Name = dFood.Name,
                                      TastyLevel = dFood.Tastyness
                                  })
                             .AsEnumerable()
                             .Select(dFood => new FoodData
                                  {
                                      Id = dFood.Id,
                                      Name = dFood.Name,
                                      TastyLevel = (Tastyness)dFood.TastyLevel
                                  });

Если вы изучите полученный SQL, вы увидите, что это проще, и вы не платите стоимость исправления объектов в ObjectContext.

Ответ 2

Используйте AsEnumerable(), чтобы превратить запрос в обычный старый запрос LINQ to Objects без необходимости создания ненужного списка

var deliciousFoods = entities.Foods
                               .Where(f => f.Tastyness == (int)Tastyness.Delicious)
                               .AsEnumerable()
                               .Select(dFood => dFood.ToFoodData())
                               .ToList();

Изменить: см. http://www.hookedonlinq.com/AsEnumerableOperator.ashx

Ответ 3

Ответ @Craig Stuntz верен, однако может возникнуть проблема, когда у вас есть несколько запросов, которые должны преобразовать объект "Food" в объект "FoodData". Вы не хотите, чтобы выражение дублировалось в нескольких местах (DRY).

Решение может состоять в том, чтобы не иметь метода, который фактически возвращает объект "FoodData", но иметь метод, который возвращает выражение, которое будет использоваться для преобразования. Затем вы можете повторно использовать этот метод.

Class Food {
  ...

  public static Expression<Func<Food, FoodData> ConvertToFoodDataExpr() {
    Expression<Func<Food, FoodData>> expr = dFood => new FoodData 
    {
      Id = dFood.Id,
      Name = dFood.Name,
      TastyLevel = dFood.Tastyness
    }
  }
}

И использовать это...

var deliciousFoods = entities.Foods
                         .Where(f => f.Tastyness == (int)Tastyness.Delicious)
                         .Select(Food.ConvertToFoodDataExpr());

Помните, что при использовании Entity Framework вы не должны материализовать IEnumerable (используя ToList, ToArray и т.д.) для применения выражения select, иначе Entity Framework не сможет сделать правильный оператор выбора, и он всегда будет выбирать все поля из таблицы.

Ответ 4

Первый .ToList() не требуется.

var deliciousFoods = entities.Food

    // Here a lazy-evaluated collection is created (ie, the actual database query
    // has not been run yet)
    .Where(f => f.Tastyness == (int)Tastyness.Delicious)

    // With ToArray, the query is executed and results returned and 
    // instances of Food created.  The database connection
    // can safely be closed at this point.
    // Given the rest of the linq query, this step can be skipped
    // with no performance penalty that I can think of
    .ToArray()

    // Project result set onto new collection.  DB Query executed if
    // not already
    // The above .ToArray() should make no difference here other
    // than an extra function call an iteration over the result set
    .Select(dFood => dFood.ToFoodData())

    // This one might not be needed, see below
    .ToList();

Требуется ли вам, чтобы в результирующем наборе был List < > ? Или будет достаточно IEnumerable или ICollection? Если это так, то последний .ToList() может не понадобиться.

Ты спросил о производительности? Сколько экземпляров вы ожидаете получить по запросу? Если это относительно немного, то .ToList() или .ToArray() или другие не имеют сколько-нибудь значимой разницы. Это больше о том, какую функциональность вам нужно раскрывать? Если возвращаемый объект должен быть индексируемым, добавочным и иметь другие свойства List, все в порядке. Но если все, что вы делаете, выполняет итерацию по возвращенной коллекции, не раскрывайте, что не нужно.