9

Linq クエリと Entity Framework (4.1) の理解を深めようとしています。次の 2 つのクエリを見てください。どちらのクエリも、車種名 (CarType.Name) を返します。

最初のクエリでは、結合を使用し、ナビゲーション プロパティ CarType を無視しました

from c in Cars.AsEnumerable().
Where(e => e.CarId == Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224"))
join ct in CarTypes on c.CarTypeId equals ct.CarTypeId
select new CarType {
    Name = ct.Name
}

次に、ナビゲーション プロパティ CarType を使用しました

from c in Cars.AsEnumerable()
where c.CarId == Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224")
select new CarType {
    Name = c.CarType.Name
}

LinqPad で両方を実行したため、Guid.Parse 関数があります。

これらを実行すると、最初のステートメントがより速く実行されます。LinqPad は 00:00:036 を報告します。2 番目のステートメントの実行が遅くなり、LinqPad は 00:00:103 を報告します

結果を見ると、ナビゲーション プロパティの代わりに結合を使用する Linq クエリの方が高速であるように見えます。本当にそうですか?誰かがこれに光を当ててください。Linq クエリを作成する際に従うべき一般的なガイダンスやベスト プラクティスはありますか?

ありがとう

4

1 に答える 1

9

を呼び出している.AsEnumerable()ため、クエリは LINQ to Entities を使用して評価されるのではなく、LINQ to Objects を使用して評価されます。

これは、最初のものは 2 回往復する可能性が高いことを意味します。1 回はすべての Car をプルし、もう 1 回はすべての CarType をプルします。次に、LINQ to Objects がそのような操作に使用するアルゴリズムを使用して、ローカルで結合を実行します。

2 つ目は、おそらく N + 1 回の往復を行っています。ここで、N は CarType の数です。すべての車を往復して取得すると、それらの車の 1 つに Entity Framework がまだ読み込まれていない CarTypeId があるたびに、データベースに戻ってその CarType が選択されます。

LINQPad の [SQL] タブを使用すると、プログラムによって実行されているすべての LINQ クエリを確認できます。

この場合に適用する必要があるベスト プラクティスは.AsEnumerable()、Entity Framework オブジェクト セットを呼び出さないことです。代わりに、クエリ全体を作成し、最後に呼び出し.ToList()て結果をキャプチャします。LINQ to Entities クエリ内では機能しない.AsEnumerable()ため、回避策として呼び出している可能性がありますが、クエリからその部分を簡単に削除できます。Guid.Parse()LINQPad でCtrl-2を押して C# ステートメント モードに切り替え、次のようなクエリを実行します。

var guid = Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224");
var carTypeNames = 
    (from c in Cars
    where c.CarId == guid
    select new CarType {
        Name = c.CarType.Name
    }).ToList();
carTypeNames.Dump();

指定された 2 つのクエリは、適切に実行された場合、ほぼ同等のパフォーマンスを発揮するはずです。そのため、より簡潔で読みやすいため、Navigation プロパティを優先する必要があります。または、好みに応じて、クエリを逆にして CarType コレクションに基づくものにすることもできます。

var guid = Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224");
var carTypeNames = 
    (from ct in CarTypes
    where ct.Cars.Any(c => c.CarId == guid)
    select new CarType {
        Name = c.CarType.Name
    }).ToList();
carTypeNames.Dump();

アップデート

次のようなエンティティ オブジェクトを作成しないでください。

public class CarTypeSummary
{
    public string Name{get;set;}
}

void Main()
{
    var guid = Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224");
    var carTypeNames = 
        (from ct in CarTypes
        where ct.Cars.Any(c => c.CarId == guid)
        select new CarTypeSummary {
            Name = c.CarType.Name
        }).ToList();
    carTypeNames.Dump();
}

運用コードでは、多くの場合、API を基礎となるデータ型から切り離して、コードをどこも変更することなく柔軟に変更できるようにすることをお勧めします。

public interface ICarTypeSummary{string Name{get;}}
public class CarTypeSummary : ICarTypeSummary
{
    public string Name{get;set;}
}
public ICarTypeSummary GetCarTypeSummaryForCar(Guid guid) 
{
    return (from ct in CarTypes
            where ct.Cars.Any(c => c.CarId == guid)
            select new CarTypeSummary {
                Name = c.CarType.Name
            }).FirstOrDefault();
}

このようにして、将来、実際の CarType を返すだけでよいと判断した場合、Entity Framework のキャッシュ メカニズムを利用するために、API をいじらずに実装を変更できます。

// Make the Entity class implement the role interface
public partial class CarType : ICarTypeSummary {}

public ICarTypeSummary GetCarTypeSummaryForCar(Guid guid) 
{
    return CarTypes.FirstOrDefault(
        ct => ct.Cars.Any(c => c.CarId == guid));
}
于 2012-07-10T15:50:23.447 に答える