计数或跳过(1).Any()我想知道是否有超过1条记录 - 实体框架

tug*_*erk 11 c# t-sql linq sql-server entity-framework

我不确定什么时候,但我读了一篇关于这个的文章,表明在使用Entity Framework时我的用法Skip(1).Any()Count()同情更好(我可能记错了).在看到生成的T-SQL代码后,我不确定这一点.

这是第一个选择:

int userConnectionCount = _dbContext.HubConnections.Count(conn => conn.UserId == user.Id);
bool isAtSingleConnection = (userConnectionCount == 1);
Run Code Online (Sandbox Code Playgroud)

这会生成以下合理的T-SQL代码:

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
  COUNT(1) AS [A1]
    FROM [dbo].[HubConnections] AS [Extent1]
    WHERE [Extent1].[UserId] = @p__linq__0
)  AS [GroupBy1]
Run Code Online (Sandbox Code Playgroud)

这是另一个选项,就我记得的建议查询:

bool isAtSingleConnection = !_dbContext
    .HubConnections.OrderBy(conn => conn.Id)
    .Skip(1).Any(conn => conn.UserId == user.Id);
Run Code Online (Sandbox Code Playgroud)

以下是上述LINQ查询生成的T-SQL:

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[UserId] AS [UserId]
        FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[UserId] AS [UserId], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
            FROM [dbo].[HubConnections] AS [Extent1]
        )  AS [Extent1]
        WHERE [Extent1].[row_number] > 1
    )  AS [Skip1]
    WHERE [Skip1].[UserId] = @p__linq__0
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM ( SELECT [Extent2].[Id] AS [Id], [Extent2].[UserId] AS [UserId]
        FROM ( SELECT [Extent2].[Id] AS [Id], [Extent2].[UserId] AS [UserId], row_number() OVER (ORDER BY [Extent2].[Id] ASC) AS [row_number]
            FROM [dbo].[HubConnections] AS [Extent2]
        )  AS [Extent2]
        WHERE [Extent2].[row_number] > 1
    )  AS [Skip2]
    WHERE [Skip2].[UserId] = @p__linq__0
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1];
Run Code Online (Sandbox Code Playgroud)

哪一个是正确的方法?这两者之间有很大的性能差异吗?

Rag*_*nti 10

查询性能取决于很多因素,例如存在的索引,实际数据,有关数据的统计信息的陈旧程度等等.SQL查询计划优化器会查看这些不同的度量标准,以提出有效的查询计划.所以,任何直截了当的答案都说查询1总是比查询2好,否则相反就是错误的.

也就是说,我在下面的回答试图解释文章的立场,以及如何Skip(1).Any()比做一个更好(略微)Count() > 1.第二个查询虽然规模较大且几乎不可读,但看起来它可以以有效的方式解释.同样,这取决于前面提到的事情.我们的想法是,数据库必须查看的行数才能得出结果Count().在计数情况下,假设所需的索引存在(Id上的聚簇索引使第二种情况下的OrderBy有效),db必须经过计数行数.在第二种情况下,它必须经过最多两行才能得出答案.

让我们的分析更加科学,看看我的上述理论是否有任何理由.为此,我正在创建一个客户虚拟数据库.Customer类型如下所示,

public class Customer
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我使用这个代码,用大约100K随机行(我真的必须证明这一点)为数据库播种,

    for (int j = 0; j < 100; j++)
    {
        using (CustomersContext db = new CustomersContext())
        {
            Random r = new Random();
            for (int i = 0; i < 1000; i++)
            {
                Customer c = new Customer
                {
                    Name = Guid.NewGuid().ToString(),
                    Age = r.Next(0, 100)
                };
                db.Customers.Add(c);
            }
            db.SaveChanges();
        }
    }
Run Code Online (Sandbox Code Playgroud)

这里有示例代码.

现在,我要使用的查询如下,

db.Customers.Where(c => c.Age == 26).Count() > 1; // scenario 1

db.Customers.Where(c => c.Age == 26).OrderBy(c => c.ID).Skip(1).Any() // scenario 2
Run Code Online (Sandbox Code Playgroud)

我已经启动了SQL分析器来捕获查询计划.捕获的计划如下所示,

场景1:

查看上图中方案1的估算成本和实际行数. 场景1  - 估计成本 场景1  - 实际行数

场景2:

在下面的图像中查看方案2的估计成本和实际行数. 场景2  - 估计成本 场景2  - 实际行数

根据初始猜测,与Count情况相比,Skip和任何情况下的估计成本和行数都较少.

结论:

抛开所有这些分析,正如许多其他人之前评论过的那样,这些并不是您应该在代码中尝试进行的性能优化.像这样的事情会损害可读性,而且很少(我会说不存在)性能上的好处.我只是为了好玩而进行了这个分析,并且决不会将其作为选择方案2的基础.我会测量并查看是否Count()真的在改变要使用的代码Skip().Any().


Ser*_*rvy 6

我读了一篇关于这个的文章,表明使用Skip(1).Any()效果比Count().

在LINQ to objects查询中,该语句非常正确.在LINQ to objects查询中,Skip(1).Any()只需要尝试获取序列的前两项,并且它可以忽略它之后的所有项.如果序列包括相当昂贵的操作(和正确执行推迟),甚至更重要的是,如果序列是无限的,这可能是一个大问题.对于大多数查询来说,它会有点重要,但往往不是很多.

对于基于查询提供程序的LINQ查询,它不太可能有显着差异.特别是EF,正如您所见,生成的查询没有明显不同.当然,是否有可能产生差异.查询提供程序可以处理一个案例比另一个案例更好,特定查询可以通过一个或另一个使用的特定重构更好地进行优化.

如果有人建议在这两者之间的EF查询中存在重大差异,那么他们错误地应用了旨在应用于LINQ to对象查询的指南.