コンテンツを読み込まずにEntityFramework内の行をカウントする方法は?


109

EntityFrameworkを使用して、テーブル上の一致する行をカウントする方法を決定しようとしています。

問題は、各行に何メガバイトものデータが(バイナリフィールドに)ある可能性があることです。もちろん、SQLは次のようになります。

SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1';

すべての行をロードし、次のようにしてCountを見つけることができます。

var owner = context.MyContainer.Where(t => t.ID == '1');
owner.MyTable.Load();
var count = owner.MyTable.Count();

しかし、それは非常に非効率的です。より簡単な方法はありますか?


編集:ありがとうございます。DBをプライベートアタッチメントから移動して、プロファイリングを実行できるようにしました。これは役立ちますが、予期しない混乱を引き起こします。

そして、私の実際のデータは少し深いので、アイテムケースパレットを運ぶトラックを使用します。少なくとも1つのアイテムがなければ、トラックを離れたくありません。

私の試みを以下に示します。CASE_2がDBサーバー(MSSQL)にアクセスすることはありません。

var truck = context.Truck.FirstOrDefault(t => (t.ID == truckID));
if (truck == null)
    return "Invalid Truck ID: " + truckID;
var dlist = from t in ve.Truck
    where t.ID == truckID
    select t.Driver;
if (dlist.Count() == 0)
    return "No Driver for this Truck";

var plist = from t in ve.Truck where t.ID == truckID
    from r in t.Pallet select r;
if (plist.Count() == 0)
    return "No Pallets are in this Truck";
#if CASE_1
/// This works fine (using 'plist'):
var list1 = from r in plist
    from c in r.Case
    from i in c.Item
    select i;
if (list1.Count() == 0)
    return "No Items are in the Truck";
#endif

#if CASE_2
/// This never executes any SQL on the server.
var list2 = from r in truck.Pallet
        from c in r.Case
        from i in c.Item
        select i;
bool ok = (list.Count() > 0);
if (!ok)
    return "No Items are in the Truck";
#endif

#if CASE_3
/// Forced loading also works, as stated in the OP...
bool ok = false;
foreach (var pallet in truck.Pallet) {
    pallet.Case.Load();
    foreach (var kase in pallet.Case) {
        kase.Item.Load();
        var item = kase.Item.FirstOrDefault();
        if (item != null) {
            ok = true;
            break;
        }
    }
    if (ok) break;
}
if (!ok)
    return "No Items are in the Truck";
#endif

また、CASE_1の結果のSQLはsp_executesqlを介してパイプされますが

SELECT [Project1].[C1] AS [C1]
FROM   ( SELECT cast(1 as bit) AS X ) AS [SingleRowTable1]
LEFT OUTER JOIN  (SELECT 
    [GroupBy1].[A1] AS [C1]
    FROM ( SELECT 
        COUNT(cast(1 as bit)) AS [A1]
        FROM   [dbo].[PalletTruckMap] AS [Extent1]
        INNER JOIN [dbo].[PalletCaseMap] AS [Extent2] ON [Extent1].[PalletID] = [Extent2].[PalletID]
        INNER JOIN [dbo].[Item] AS [Extent3] ON [Extent2].[CaseID] = [Extent3].[CaseID]
        WHERE [Extent1].[TruckID] = '....'
    )  AS [GroupBy1] ) AS [Project1] ON 1 = 1

[ トラック、ドライバー、パレット、ケース、アイテムは本当に持っていません。SQLからわかるように、Truck-PalletとPallet-Caseの関係は多対多ですが、重要ではないと思います。私の実際のオブジェクトは無形で説明が難しいため、名前を変更しました。]


1
どのようにしてパレット積載問題を解決しましたか?
シャーロック

回答:


123

クエリ構文:

var count = (from o in context.MyContainer
             where o.ID == '1'
             from t in o.MyTable
             select t).Count();

メソッド構文:

var count = context.MyContainer
            .Where(o => o.ID == '1')
            .SelectMany(o => o.MyTable)
            .Count()

どちらも同じSQLクエリを生成します。


なぜSelectMany()?必要ですか?それなしでは適切に動作しませんか?
Jo Smo、2015

@JoSmo、いや、それはまったく別のクエリです。
Craig Stuntz、2015

整理してくれてありがとう。確認したかっただけです。:)
Jo Smo

1
SelectManyとの違いは何ですか?わかりません。SelectManyを使用せずにそれを実行しますが、2,000万件を超えるレコードがあるため、非常に遅くなります。私はヤン・チャンからの回答を試してみましたが、うまく機能しました。SelectManyの機能を知りたいだけでした。
mikesoft 2016年

1
@AustinFelipe SelectManyを呼び出さないと、クエリは、IDが「1」のMyContainerの行数を返します。SelectMany呼び出しは、クエリの以前の結果(つまり、の結果MyContainer.Where(o => o.ID == '1'))に属するMyTableのすべての行を返します
sbecker

48

あなたは何かが欲しいと思います

var count = context.MyTable.Count(t => t.MyContainer.ID == '1');

(コメントを反映するように編集)


1
いいえ、彼はMyContainerにID = 1と1つのエンティティによって参照されたMyTable内のエンティティの数を必要とする
クレイグStuntz

3
ちなみに、t.IDがPKの場合、上記のコードのカウントは常に 1になります。:)
Craig Stuntz 2009年

2
@クレイグ、あなたは正しい、私はt.ForeignTable.IDを使用するべきだった。更新しました。
ケビン

1
これは短くて簡単です。私の選択は次のとおりです: var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); 長くて醜い: var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); コーディングスタイルによって異なります...
CL

「System.Linqを使用する」が含まれていることを確認してください。そうしないと機能しません
CountMurphy

16

私が理解しているように、選択された回答は、関連するすべてのテストをロードします。このmsdnブログによると、より良い方法があります。

http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

具体的には

using (var context = new UnicornsContext())

    var princess = context.Princesses.Find(1);

    // Count how many unicorns the princess owns 
    var unicornHaul = context.Entry(princess)
                      .Collection(p => p.Unicorns)
                      .Query()
                      .Count();
}

3
追加のFind(1)リクエストは必要ありません。ただ、エンティティを作成し、コンテキストにアタッチ:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
tenbits

13

これは私のコードです:

IQueryable<AuctionRecord> records = db.AuctionRecord;
var count = records.Count();

変数がIQueryableとして定義されていることを確認してください。Count()メソッドを使用すると、EFは次のように実行します

select count(*) from ...

それ以外の場合、レコードがIEnumerableとして定義されていると、生成されたSQLはテーブル全体をクエリし、返された行をカウントします。


10

まあ、SELECT COUNT(*) FROM TableSQL Serverは実際に全テーブルスキャン(クラスター化インデックススキャン)しかできないので、特に大きなテーブルではかなり効率が悪くなります。

場合によっては、データベースからのおよその行数を知っていれば十分であり、そのような場合は、次のようなステートメントで十分です。

SELECT 
    SUM(used_page_count) * 8 AS SizeKB,
    SUM(row_count) AS [RowCount], 
    OBJECT_NAME(OBJECT_ID) AS TableName
FROM 
    sys.dm_db_partition_stats
WHERE 
    OBJECT_ID = OBJECT_ID('YourTableNameHere')
    AND (index_id = 0 OR index_id = 1)
GROUP BY 
    OBJECT_ID

これにより、動的管理ビューが検査され、特定のテーブルが指定されている場合、そこから行数とテーブルサイズが抽出されます。これは、ヒープ(index_id = 0)またはクラスター化インデックス(index_id = 1)のエントリを合計することによって行われます。

迅速で使いやすいですが、100%正確または最新であるとは限りません。しかし、多くの場合、これで「十分」であり、サーバーへの負荷も大幅に軽減されます。

多分それもあなたのために働くでしょうか?もちろん、EFで使用するには、これをストアドプロシージャにラップするか、直接の「SQLクエリの実行」呼び出しを使用する必要があります。

マーク


1
WHEREでのFK参照により、テーブル全体をスキャンすることはできません。マスターの詳細のみがスキャンされます。彼が抱えていたパフォーマンスの問題は、レコード数ではなくblobデータのロードにありました。通常、マスターレコードごとに数十以上の詳細レコードがないと仮定すると、実際には遅くないものを「最適化」しません。
クレイグスタンツ2009年

OK、はい、その場合、サブセットのみを選択します-それで問題ありません。blobデータについては、EFテーブルの任意の列に「据え置きロード」を設定してロードを回避できるので、役立つかもしれないという印象を受けました。
marc_s 2009年

EntityFrameworkでこのSQLを使用する方法はありますか?とにかく、この場合、一致する行があることを知る必要があるだけでしたが、より一般的に意図的に質問しました。
NVRAM

4

エンティティコンテキストのExecuteStoreQueryメソッドを使用します。これにより、結果セット全体をダウンロードしたり、単純な行カウントを行うためにオブジェクトに逆シリアル化したりする必要がなくなります。

   int count;

    using (var db = new MyDatabase()){
      string sql = "SELECT COUNT(*) FROM MyTable where FkId = {0}";

      object[] myParams = {1};
      var cntQuery = db.ExecuteStoreQuery<int>(sql, myParams);

      count = cntQuery.First<int>();
    }

6
あなたが書いたint count = context.MyTable.Count(m => m.MyContainerID == '1')場合、生成されたSQLはあなたがやっていることと正確に似ていますが、コードははるかに優れています。そのため、エンティティーはメモリーにロードされません。必要に応じて、LINQPadで試してください。内部で使用されているSQLが表示されます。
Drew Noakes

インラインSQL。。私の好きなものではありません。
デュアン

3

これはうまくいくと思います...

var query = from m in context.MyTable
            where m.MyContainerId == '1' // or what ever the foreign key name is...
            select m;

var count = query.Count();

これも最初に行った方向ですが、手動で追加しない限り、mにはMyContainerプロパティがありますが、MyContainerIdはないことは理解しています。したがって、調べたいのはm.MyContainer.IDです。
ケビン

MyContainerが親でMyTableがリレーションシップの子である場合、外部キーとの関係を確立する必要がありましたが、MyContainerエンティティに関連付けられているMyTableエンティティを他にどのように知ることができるかわかりません...構造についての仮定を行った...
bytebender
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.