如何在不加载内容的情况下在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();

但这是完全无效的。有没有更简单的方法?


编辑:谢谢,所有。我已将数据库从专用附件中移出,以便可以进行性能分析;这会有所帮助,但会引起我意想不到的混乱。

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

我的尝试如下所示。我不了解的部分是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
您如何解决托盘装载问题?
Sherlock

Answers:


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 2015年

1
你能告诉我为什么SelectMany与众不同吗?我不明白 我在没有SelectMany的情况下执行此操作,但由于我拥有超过2000万条记录,它的运行速度确实很慢。我尝试了Yang Zhang的回答,效果很好,只是想知道SelectMany的功能。
mikesoft '16

1
@AustinFelipe如果不调用SelectMany,则查询将返回MyContainer中ID等于“ 1”的行数。SelectMany调用返回MyTable中属于查询先前结果(表示的结果MyContainer.Where(o => o.ID == '1'))的所有行
sbecker

48

我想你想要类似的东西

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

(编辑以反映评论)


1
不,他需要由MyContainer中ID = 1的一个实体引用的MyTable中的实体计数
Craig Stuntz 09年

3
顺便说一句,如果t.ID是PK,则上面的代码中的计数始终为1::)
Craig Stuntz

2
@Craig,您是对的,我应该使用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 2016年

13

这是我的代码:

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

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

select count(*) from ...

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


10

好吧,即使SELECT COUNT(*) FROM TableSQL Server效率也相当低,尤其是在大型表上,因为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中使用它,您必须将其包装在存储的proc中,或使用直接的“执行SQL查询”调用。

马克


1
由于WHERE中有FK引用,因此不会进行全表扫描。仅扫描母版的详细信息。他遇到的性能问题来自加载Blob数据,而不是记录计数。假设每个主记录通常没有几十个+详细记录,我不会“优化”一些实际上并不慢的东西。
2009年

好的,是的,在那种情况下,您将只选择一个子集-应该没问题。至于Blob数据-给我的印象是,您可以在任何EF表的任何列上设置“延迟加载”以避免加载它,因此可能会有所帮助。
marc_s 2009年

有没有办法将此SQL与EntityFramework一起使用?无论如何,在这种情况下,我只需要知道存在匹配的行,但是我有意地更普遍地问了这个问题。
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。
德鲁·诺阿克斯

内联SQL。。不是我最喜欢的东西。
杜安(Duanne)

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是关系中的子级,则您必须使用一些外键建立该关系,我不确定您还如何知道哪个MyTable实体与MyContainer实体相关联...但是也许我对结构做了一个假设...
bytebender
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.