7

次の不自然なエンティティ オブジェクトを考えてみましょう。

public class Consumer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool NeedsProcessed { get; set; }
    public virtual IList<Purchase> Purchases { get; set; }  //virtual so EF can lazy-load
}

public class Purchase
{
    public int Id { get; set; }
    public decimal TotalCost { get; set; }
    public int ConsumerId { get; set; }
}

ここで、このコードを実行したいとしましょう:

var consumers = Consumers.Where(consumer => consumer.NeedsProcessed);

//assume that ProcessConsumers accesses the Consumer.Purchases property
SomeExternalServiceICannotModify.ProcessConsumers(consumers);

デフォルトでは、これは ProcessConsumers メソッド内の Select N+1 の影響を受けます。消費者を列挙するときにクエリをトリガーし、各購入コレクションを 1 つずつ取得します。この問題の標準的な解決策は、インクルードを追加することです。

var consumers = Consumers.Include("Purchases").Where(consumer => consumer.NeedsProcessed);

//assume that ProcessConsumers accesses the Consumer.Purchases property
SomeExternalServiceICannotModify.ProcessConsumers(consumers);

多くの場合、これで問題なく動作しますが、複雑なケースでは、インクルードによってパフォーマンスが桁違いに低下する可能性があります。次のようなことは可能ですか?

  1. 消費者をつかむ、var consumer = _entityContext.Consumers.Where(...).ToList()
  2. 私の購入をつかむ、var purchases = _entityContext.Purchases.Where(...).ToList()
  3. 消費者に潤いを与えます。すでにメモリにロードされている購入からコレクションを手動で購入します。次に、それを ProcessConsumers に渡すと、それ以上の db クエリはトリガーされません。

#3 やり方がわかりません。遅延ロード (したがって Select N+1) をトリガーする consumer.Purchases コレクションにアクセスしようとするとします。おそらく、Consumer を適切な型 (EF プロキシ型ではなく) にキャストしてから、コレクションをロードする必要がありますか? このようなもの:

foreach (var consumer in Consumers)
{
     //since the EF proxy overrides the Purchases property, this doesn't really work, I'm trying to figure out what would
     ((Consumer)consumer).Purchases = purchases.Where(x => x.ConsumerId = consumer.ConsumerId).ToList();
}

編集: 問題をより明確に明らかにするために、例を少し書き直しました。

4

4 に答える 4

1

私の理解が正しければ、フィルタリングされた消費者のサブセットと、それぞれの購入のフィルタリングされたサブセットを 1 つのクエリでロードしたいと考えています。それが正しくない場合は、意図を理解した上でお許しください。それが正しければ、次のようなことができます。

var consumersAndPurchases = db.Consumers.Where(...)
    .Select(c => new {
        Consumer = c,
        RelevantPurchases = c.Purchases.Where(...)
    })
    .AsNoTracking()
    .ToList(); // loads in 1 query

// this should be OK because we did AsNoTracking()
consumersAndPurchases.ForEach(t => t.Consumer.Purchases = t.RelevantPurchases);

CannotModify.Process(consumersAndPurchases.Select(t => t.Consumer));

Process 関数がコンシューマ オブジェクトを変更し、それらの変更をデータベースにコミットすることを期待している場合、これは機能しないことに注意してください。

于 2012-08-11T13:50:26.907 に答える
0

以下に示すように、基本的に特定のエンティティの代わりにプロジェクションを返すことにより、データベースで作業を行うことにより、多くのラウンドトリップまたは非効率的なクエリ生成の問題を回避することはできませんか?

var query = from c in db.Consumers
            where c.Id > 1000
            select new { Consumer = c, Total = c.Purchases.Sum( p => p.TotalCost ) };
var total = query.Sum( cp => cp.Total );

私は決して EF の専門家ではないので、この手法が適切でない場合はご容赦ください。

于 2012-06-09T19:00:27.020 に答える
0

消費者をつかむ

var consumers = _entityContext.Consumers
                              .Where(consumer => consumer.Id > 1000)
                              .ToList();

私の購入をつかむ

var purchases = consumers.Select(x => new {
                                       Id = x.Id,
                                       IList<Purchases> Purchases = x.Purchases         
                                       })
                         .ToList()
                         .GroupBy(x => x.Id)
                         .Select( x => x.Aggregate((merged, next) => merged.Merge(next)))
                         .ToList();

消費者に潤いを与えます。すでにメモリにロードされている購入からコレクションを手動で購入します。

for(int i = 0; i < costumers.Lenght; i++)
   costumers[i].Purchases = purchases[i];
于 2012-06-09T17:43:10.380 に答える