如何在不加载内容的情况下COUNT在EntityFramework中的行?

NVR*_*RAM 102 c# database entity-framework

我正在尝试确定如何使用EntityFramework 计算表上的匹配行.

问题是每行可能有许多兆字节的数据(在二进制字段中).当然SQL会是这样的:

SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1';
Run Code Online (Sandbox Code Playgroud)

我可以加载所有行,然后找到Count:

var owner = context.MyContainer.Where(t => t.ID == '1');
owner.MyTable.Load();
var count = owner.MyTable.Count();
Run Code Online (Sandbox Code Playgroud)

但这非常低效.有更简单的方法吗?


编辑:谢谢,所有.我已将数据库从私有附件移动,因此我可以运行分析; 这有助于但却引起我没想到的混乱.

而我的真实数据是深一点,我会用卡车运送托盘案件资料 -我不想让卡车离开除非有至少一个项目在里面.

我的尝试如下所示.我没有得到的部分是CASE_2永远不会访问数据库服务器(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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

[ 我真的没有卡车,司机,托盘,箱子或物品; 正如您从SQL中看到的那样,Truck-Pallet和Pallet-Case关系是多对多的 - 尽管我认为这并不重要.我的真实对象是无形的,难以描述,所以我更改了名称.]

Cra*_*ntz 115

查询语法:

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

方法语法:

var count = context.MyContainer
            .Where(o => o.ID == '1')
            .SelectMany(o => o.MyTable)
            .Count()
Run Code Online (Sandbox Code Playgroud)

两者都生成相同的SQL查询.

  • 你能告诉我为什么它与 SelectMany 不同吗?我不明白。我在没有 SelectMany 的情况下执行此操作,但速度非常慢,因为我有超过 2000 万条记录。我尝试了杨张的答案,效果很好,只是想知道 SelectMany 的作用。 (2认同)
  • @AustinFelipe 如果不调用 SelectMany,查询将返回 MyContainer 中 ID 等于“1”的行数。SelectMany 调用返回 MyTable 中属于上一个查询结果的所有行(即“MyContainer.Where(o => o.ID == '1')”的结果) (2认同)

Kev*_*vin 44

我想你想要的东西

var count = context.MyTable.Count(t => t.MyContainer.ID == '1');
Run Code Online (Sandbox Code Playgroud)

(编辑以反映评论)

  • 顺便提一下,如果t.ID是PK,那么上面代码中的count将*总是*为1. :) (3认同)
  • @Craig,你是对的,我应该使用t.ForeignTable.ID.更新. (2认同)
  • 嗯,这是简短而简单的。我的选择是:`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();` 但这取决于编码风格... (2认同)

Qui*_*orn 14

据我了解,所选答案仍然会加载所有相关测试.根据这个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();
}
Run Code Online (Sandbox Code Playgroud)

  • 没有必要额外添加`Find(1)`请求.只需创建实体并附加到上下文:`var princess = new PrincessEntity {Id = 1}; context.Princesses.Attach(格格);` (3认同)

Yan*_*ang 10

这是我的代码:

IQueryable<AuctionRecord> records = db.AuctionRecord;
var count = records.Count();
Run Code Online (Sandbox Code Playgroud)

确保变量定义为IQueryable,然后当您使用Count()方法时,EF将执行类似的操作

select count(*) from ...
Run Code Online (Sandbox Code Playgroud)

否则,如果记录定义为IEnumerable,则生成的sql将查询整个表并计算返回的行数.


mar*_*c_s 9

好吧,即使SELECT COUNT(*) FROM Table是相当低效的,特别是在大型表上,因为SQL 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
Run Code Online (Sandbox Code Playgroud)

这将检查动态管理视图,并在给定特定表的情况下从中提取行数和表大小.它通过总结堆的条目(index_id = 0)或聚簇索引(index_id = 1)来实现.

它很快,易于使用,但不保证100%准确或最新.但在许多情况下,这"足够好"(并且减轻了服务器的负担).

也许那对你也有用吗?当然,要在EF中使用它,您必须将其包装在存储过程中或使用直接的"执行SQL查询"调用.


goo*_*ack 6

使用实体上下文的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>();
    }
Run Code Online (Sandbox Code Playgroud)

  • 如果您编写`int count = context.MyTable.Count(m =&gt; m.MyContainerID == '1')`,那么生成的SQL 将与您正在执行的操作完全相似,但代码要好得多。没有实体被加载到内存中。如果您愿意,可以在 LINQPad 中尝试一下——它会向您展示在幕后使用的 SQL。 (6认同)