2

LINQ を使用して、効率的な方法で DbContext をクエリするのに問題があります。データベースには、日付、名前、その他の情報を持つ 700,000 を超えるエンティティが含まれています。

私のコードでは、オブジェクトの新しいリスト (潜在的に 100,000 の要素を持つ可能性があります) が入ってきて、データベースにクエリを実行して、どの情報が新しいエンティティであるか、またはどの情報が更新が必要な既存のエンティティであるかを推測したいと考えています。

非常に効率的な方法で(可能であれば単一のクエリで)実行したいと思います。

これは私のコードです:

public class MyDbContext : DbContext
    {
        public DbSet<MyEntity> MyEntities { get; set; }
    }

    public class MyEntity
    {
        [Key]
        public Guid Id { get; set; }
        public DateTime Date { get; set; }
        public string Name { get; set; }
        public double Amount { get; set; }
        public string Description { get; set; }
    }

    public class IncomingInfo
    {
        public DateTime Date { get; set; }
        public string Name { get; set; }
        public double Amount { get; set; }
    }

    public class Modifier
    {
        public void AddOrUpdate(IList<IncomingInfo> info)
        {
            using (var context = new MyDbContext())
            {                 
                //Find the new information 
                //to add as new entities
                IEnumerable<MyEntity> EntitiesToAdd = ??

                //Find the information 
                //to update in existing entities
                IEnumerable<MyEntity> EntitiesToUpdate = ?? 
            }
        }
    }

誰かがクエリの作成を手伝ってくれますか? どうもありがとうございました。

編集:申し訳ありませんが、2 つのエンティティが等しいと見なす方法を説明するのを忘れていました。Date プロパティと Name プロパティが同じ場合は、等しいです。

最初に LinqKit PredicateBuilder を使用して述語を作成しようとしましたが、あまり成功しませんでした (パラメーターが大きすぎるというエラーが発生し、複数のクエリを作成する必要があり、時間がかかりました)。

これまでのところ、私が見つけた最も成功した方法は、LEFT OUTER 結合を実装し、次の方法で実装した DbSet に受信リストを結合することでした。

var values = info.GroupJoin(context.MyEntities,
                    inf => inf.Name + inf.Date.ToString(),
                    ent => ent.Name + ent.Date.ToString(),
                    (inf, ents) => new { Info = inf, Entities = ents })
                 .SelectMany(i => i.Entities.DefaultIfEmpty(),
                    (i, ent) => new { i.Info.Name, i.Info.Amount, i.Info.Date, ToBeAdded = ent == null ? true : false });

IEnumerable<MyEntity> EntitiesToAdd = values.Where(i => i.ToBeAdded)
    .Select(i => new MyEntity
    {
        Id = Guid.NewGuid(),
        Amount = i.Amount,
        Date = i.Date,
        Name = i.Name,
        Description = null
    }).ToList();

私のテストには、データベースに 700,000 のエンティティが含まれています。着信情報リストには 70,000 項目が含まれています。ここで、50,000 は既存のエンティティで、20,000 は新しいエンティティです。このクエリの実行には約 15 秒かかりますが、これは私には正しくないようです。

うまくいけば、これで助けを求めるのに十分です。誰かがこれを手伝ってくれますか?どうもありがとうございました。

4

1 に答える 1

1

@Leniency からのペーストビンの応答を読んだところ、日付範囲のクエリとそこでの比較など、私が言おうとしていたのと同じことがいくつかカバーされています。ただし、この方法の問題点は、(これらの日付の設定方法によっては) データベース内の 70 万件以上のレコードがすべて返される可能性があり、絶対に最悪のパフォーマンスが得られることです。

私の提案は、ネットワーク トポロジを分析して、データベースへの呼び出しが実際にどれだけ高価かを確認することです。IncomingInfoこれは、クライアントからこれらのオブジェクトを受信して​​いる (Web) サーバー上で実行されていると想定しています。このサーバーがデータベース サーバー (または同じマシン上) に密接に接続されている場合は、データベースへの呼び出しを最適化しない方がよい場合があります。

また、クライアントの動作を制御できる場合は、要求ごとに 25 から 100 レコードのみを送信するようにクライアントに強制することもできます。これにより、より管理しやすいチャンクでそれらを処理できるようになります。クライアントはサーバーに 100 件以上のリクエストを送信する必要があるかもしれません (予想される負荷プロファイルに応じて、一度に最大 5 件送信されるように非同期にすることもできます) が、少なくとも 5 件以上はそこに留まることはありません。 1 つの要求に対してサーバーから応答が返されるまでの待機時間 (分)。

ところで、GroupJoinあなたが 15 秒かかったという呼び出しは、おそらく、参加する前に 700K レコードすべてをダウンロードする必要があるためです。同じマシン上に存在しないオブジェクトに対して結合を行うことはできません。すべてのIncomingInfoオブジェクト (または少なくとも Name+Date.ToString() の連結) をデータベースに送信する必要があります。結合を行う前に、データベースからすべてのレコードを要求します。どのメソッドが使用されているかを知るには、おそらくデータベースに送信されている SQL を確認する必要があります。しかし、この場合、一度に 1 つずつ一致するようにデータベースにクエリを実行する方が、おそらく結合よりも高速であることがわかるでしょう。

それが役立つことを願っています! ;)

于 2013-07-10T21:34:11.890 に答える