7

我阅读了团队的加载相关实体帖子,Entity Framework对最后一段感到有些困惑:

有时,知道有多少实体与数据库中的另一个实体相关而不实际产生加载所有这些实体的成本是很有用的。可以使用带有 LINQ Count 方法的 Query 方法来执行此操作。例如:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Count how many posts the blog has 
    var postCount = context.Entry(blog)
                          .Collection(b => b.Posts)
                          .Query()
                          .Count();
}

为什么这里需要Query+Count方法?
我们不能简单地使用 LINQ 的COUNT方法吗?

var blog = context.Blogs.Find(1);
var postCount = blog.Posts.Count();

这会触发延迟加载并且所有集合都将被加载到内存中,然后我会得到我想要的标量值吗?

4

3 回答 3

6

您将在机器人案例中获得所需的标量值。但是考虑一下正在发生的事情的不同。

随着.Query().Count()您在表单的数据库上运行查询SELECT COUNT(*) FROM Posts并将该值分配给您的整数变量。

使用.Posts.Count,您可以在数据库上运行(类似的东西)SELECT * FROM Posts(已经贵得多)。然后,在枚举集合以查找您的计数时,结果的每一行逐个字段映射到您的 C# 对象类型。通过以这种方式请求计数,您将强制加载所有数据,以便 C# 可以计算有多少。

希望很明显,向数据库询问行数(实际上不返回所有这些行)效率更高!

于 2012-09-20T10:42:38.120 回答
2

第一种方法没有加载所有行,因为该Count方法是从 an 调用的,IQueryable但第二种方法是加载所有行,因为它是从 an 调用的ICollection

我做了一些测试来验证它。我用 Table1 和 Table2 对其进行了测试,其中 Table1 具有 PK“Id”,而 Table2 具有 FK“Id1”(1:N)。我从这里http://efprof.com/使用了 EF 分析器。

第一种方法:

var t1 = context.Table1.Find(1);

var count1 = context.Entry(t1)
                        .Collection(t => t.Table2)
                        .Query()
                        .Count();

Select * From Table2

SELECT TOP (2) [Extent1].[Id] AS [Id]
FROM   [dbo].[Table1] AS [Extent1]
WHERE  [Extent1].[Id] = 1 /* @p0 */

SELECT [GroupBy1].[A1] AS [C1]
FROM   (SELECT COUNT(1) AS [A1]
        FROM   [dbo].[Table2] AS [Extent1]
        WHERE  [Extent1].[Id1] = 1 /* @EntityKeyValue1 */) AS [GroupBy1]

第二种方法:

var t1 = context.Table1.Find(1);
var count2 = t1.Table2.Count(); 

表2被加载到内存中:

SELECT TOP (2) [Extent1].[Id] AS [Id]
FROM   [dbo].[Table1] AS [Extent1]
WHERE  [Extent1].[Id] = 1 /* @p0 */

SELECT [Extent1].[Id]  AS [Id],
       [Extent1].[Id1] AS [Id1]
FROM   [dbo].[Table2] AS [Extent1]
WHERE  [Extent1].[Id1] = 1 /* @EntityKeyValue1 */

为什么会这样?

的结果Collection(t => t.Table2)是一个实现ICollection但它没有加载所有行并且有一个名为 的属性的类IsLoaded。该Query方法的结果是一个IQueryable,这允许Count在不预加载行的情况下调用。

的结果t1.Table2是一个ICollection,它正在加载所有行以获取计数。顺便说一句,即使您只使用t1.Table2而不询问计数,行也会加载到内存中。

于 2012-09-20T12:38:56.443 回答
1

第一个解决方案不会触发延迟加载,因为它很可能永远不会直接访问集合属性。该Collection方法接受Expression,而不仅仅是委托。它仅用于获取属性的名称,然后用于访问映射信息和构建正确的查询。

即使它会访问集合属性,它也可以使用与 EF 的其他内部部分(例如验证)相同的策略,即在访问导航属性之前暂时关闭延迟加载以避免意外的延迟加载。

顺便提一句。与构建查询需要访问导航属性的 ObjectContext API 相比,这是一个巨大的改进,因此它可能会触发延迟加载。

这两种方法之间还有一个区别:

  • 第一个总是对数据库执行查询并返回数据库中的项目数
  • 第二个只对数据库执行一次查询以加载所有项目,然后返回应用程序中的项目计数而不检查数据库中的状态

作为第三个非常有趣的选项,您可以使用额外的加载。Arthur Vickers 的实现展示了如何使用导航属性从数据库中获取计数,而无需延迟加载项目。

于 2012-09-20T12:15:32.030 回答