【问题标题】:How to COUNT rows within EntityFramework without loading contents?如何在不加载内容的情况下计算 EntityFramework 中的行?
【发布时间】:2009-05-20 21:35:16
【问题描述】:

我正在尝试确定如何计数使用 EntityFramework 的表中的匹配行。

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

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

我可以加载所有行并然后找到计数:

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

但这是非常低效的。有没有更简单的方法?


编辑:谢谢大家。我已将数据库从私人附件中移出,以便我可以运行分析;这会有所帮助,但会引起我没想到的混乱。

而且我的真实数据更深一些,我将使用 Trucks 运送 PalletsCasesItems > -- 我不想让卡车离开,除非里面至少有一个Item

我的尝试如下所示。我不明白的部分是 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

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 关系是多对多的——尽管我认为这并不重要。我的实物是无形的,更难描述,所以我改了名字。]

【问题讨论】:

  • 你们是如何解决托盘装载问题的?

标签: c# database entity-framework


【解决方案1】:

查询语法:

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()?需要吗?没有它就不能正常工作吗?
  • @JoSmo,不,这是一个完全不同的查询。
  • 感谢您为我解决这个问题。只是想确定一下。 :)
  • 你能告诉我为什么它与 SelectMany 不同吗?我不明白。我在没有 SelectMany 的情况下这样做,但它变得非常慢,因为我有超过 2000 万条记录。我尝试了 Yang Zhang 的答案并且效果很好,只是想知道 SelectMany 的作用。
  • @AustinFelipe 如果不调用 SelectMany,查询将返回 MyContainer 中 ID 等于“1”的行数。 SelectMany 调用返回 MyTable 中属于上一个查询结果的所有行(即MyContainer.Where(o => o.ID == '1') 的结果)
【解决方案2】:

我想你想要类似的东西

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

(已编辑以反映 cmets)

【讨论】:

  • 不,他需要 MyContainer 中 ID = 1 的一个实体引用的 MyTable 中的实体计数
  • 顺便说一下,如果 t.ID 是一个 PK,那么上面代码中的 count 将始终为 1。:)
  • @Craig,你说得对,我应该使用 t.ForeignTable.ID。已更新。
  • 这很简短。我的选择是: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();但这取决于编码风格...
  • 确保包含“使用 System.Linq”,否则这将不起作用
【解决方案3】:

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

【讨论】:

  • 不需要额外的Find(1)请求。只需创建实体并附加到上下文:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
【解决方案4】:

这是我的代码:

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

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

select count(*) from ...

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

【讨论】:

    【解决方案5】:

    好吧,即使是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
    

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

    它快速、易于使用,但不能保证 100% 准确或最新。但在许多情况下,这已经“足够好”了(并且给服务器带来的负担要少得多)。

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

    马克

    【讨论】:

    • 由于 WHERE 中的 FK 引用,它不会是全表扫描。只会扫描主人的详细信息。他遇到的性能问题是加载 blob 数据,而不是记录数。假设每个主记录通常没有数万个详细记录,我不会“优化”实际上并不慢的东西。
    • 好的,是的,在这种情况下,您将只选择一个子集 - 这应该没问题。至于 blob 数据 - 我的印象是您可以在任何 EF 表中的任何列上设置“延迟加载”以避免加载它,这样可能会有所帮助。
    • 有没有办法将此 SQL 与 EntityFramework 一起使用?无论如何,在这种情况下,我只需要知道有匹配的行,但我故意提出更笼统的问题。
    【解决方案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>();
        }
    

    【讨论】:

    • 如果您编写int count = context.MyTable.Count(m =&gt; m.MyContainerID == '1'),则生成的 SQL 将与您正在执行的操作完全相同,但代码要好得多。没有实体被加载到内存中。如果您愿意,可以在 LINQPad 中尝试一下——它会向您展示幕后使用的 SQL。
    • 内嵌 SQL 。 .不是我最喜欢的东西。
    【解决方案7】:

    我认为这应该可行...

    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 是关系中的子级,那么您必须使用某个外键建立该关系,我不确定您如何知道哪些 MyTable 实体与MyContainer 实体...但也许我对结构做了一个假设...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-18
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-30
    • 1970-01-01
    相关资源
    最近更新 更多