web-dev-qa-db-ja.com

エンティティフレームワーク最初に.ToList()なしで新しいPOCOを選択します

サービスレイヤー(WCF Webサイト)とSilverlight4クライアントを使用してアプリケーションを作成しています。 RIAサービスはオプションではないため、やり取りする中間クラスを作成します。この質問の目的のために、私がおいしいFoodオブジェクトを行ったり来たりしていると仮定しましょう。

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

EFモデルは基本的に同じクラスであり、3つの基本フィールドを持つテーブルです(Tastynessは、列挙型Tastynessに対応するintです)。

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がカスタムメソッドを同等のクエリに変換できないという例外が発生します。これは理解できます。

私の質問は、。Select(...)の前に。ToList()を呼び出して、オブジェクトをFoodオブジェクトのPOCOバージョンに変換するカスタム拡張機能を使用することについてです。 。

ここで行うより良いパターンはありますか、それとも.ToList()のより良い代替手段であり、List <..>結果の機能を実際には必要としないため、パフォーマンスが向上する可能性があります。

15
Jacob

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を調べると、SQLがより単純であり、オブジェクトをObjectContextに修正するコストを支払わないことがわかります。

12
Craig Stuntz

AsEnumerable()を使用すると、不要なリストを作成せずに、クエリを通常の古いLINQ toObjectsクエリに変換できます。

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

編集:参照 http://www.hookedonlinq.com/AsEnumerableOperator.ashx

6
cordialgerm

@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を使用するときは、select式を適用する前にIEnumerableを(ToList、ToArrayなどを使用して)マテリアライズしないでください。そうしないと、Entity Frameworkは正しいselectステートメントを作成できず、常にからすべてのフィールドが選択されます。テーブル。

1
Björn Boxstart

最初の.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();

結果セットがリスト<>である必要がありますか?それとも、IEnumerableまたはICollectionだけで十分でしょうか?その場合、最後の.ToList()は必要ない場合があります。

パフォーマンスについて質問しましたか?クエリごとにいくつのインスタンスが返されると思いますか?比較的少ない場合は、.ToList()や.ToArray()などは意味のある違いをもたらしません。それは、どのような機能を公開する必要があるかということです。返されたオブジェクトがインデックス可能で追加可能であり、Listの他のプロパティを持つ必要がある場合は、問題ありません。ただし、返されたコレクションを反復処理するだけの場合は、不要なものを公開しないでください。

0
Leniency