c# - 如何让EF高效调用聚合函数?

标签 c# sql-server entity-framework linq linq-to-entities

我正在尝试编写一个 LINQ-to-entities 查询,该查询将采用主对象的 ICollection 导航属性,并将一些元数据附加到每个对象,这些元数据是通过连接每个对象来确定的到另一个数据库表并使用聚合函数。所以主要对象是这样的:

public class Plan
{
    ...
    public virtual ICollection<Room> Rooms { get; set; }
}

我的查询是这样的:

var roomData = (
    from rm in plan.Rooms
    join conf in context.Conferences on rm.Id equals conf.RoomId into cjConf
    select new {
        RoomId = rm.Id,
        LastUsedDate = cjConf.Count() == 0 ? (DateTime?)null : cjConf.Max(conf => conf.EndTime)
    }
).ToList();

我想要的是它生成一些高效的 SQL,使用聚合函数 MAX 来计算 LastUsedDate,如下所示:

SELECT
    rm.Id, MAX(conf.EndTime) AS LastUsedDate
FROM
    Room rm
LEFT OUTER JOIN
    Conference conf ON rm.Id = conf.RoomId
WHERE
    rm.Id IN ('a967c9ce-5608-40d0-a586-e3297135d847', '2dd6a82d-3e76-4441-9a40-133663343d2b', 'bb302bdb-6db6-4470-a24c-f1546d3e6191')
GROUP BY
    rm.id

但是当我分析 SQL Server 时,它显示来自 EF 的此查询:

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[RoomId] AS [RoomId], 
    [Extent1].[ProviderId] AS [ProviderId], 
    [Extent1].[StartTime] AS [StartTime], 
    [Extent1].[EndTime] AS [EndTime], 
    [Extent1].[Duration] AS [Duration], 
    [Extent1].[ParticipantCount] AS [ParticipantCount], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[ServiceType] AS [ServiceType], 
    [Extent1].[Tag] AS [Tag], 
    [Extent1].[InstantMessageCount] AS [InstantMessageCount]
    FROM [dbo].[Conference] AS [Extent1]

因此,它从 Conference 中选择所有内容,并在内存中进行 Max() 计算,这是非常低效的。如何让 EF 使用聚合函数生成正确的 SQL 查询?

最佳答案

等效的 LINQ to Entities 查询紧密地转换为您要执行的 SQL 查询,如下所示:

var roomIds = plan.Rooms.Select(rm => rm.Id);

var query =
    from rm in context.Rooms
    join conf in context.Conferences on rm.Id equals conf.RoomId
    into rmConf from rm in rmConf.DefaultIfEmpty() // left join
    where roomIds.Contains(rm.Id)
    group conf by rm.Id into g
    select new
    {
        RoomId = g.Key,
        LastUsedDate = g.Max(conf => (DateTime?)conf.EndTime)
    };

技巧是从 EF IQueryable 开始查询,从而允许它完全转换为 SQL,而不是从 plan.Rooms正如所讨论的查询中那样 IEnumerable并使整个查询在内存中执行( context.Conferences 被视为 IEnumerable 并导致将整个表加载到内存中)。

SQL IN子句是通过内存 IEnumerable<Guid> 实现的和Contains方法。

最后,不需要检查计数了。 SQL自然处理null s,您所需要的只是确保调用可为空的 Max过载,这是通过 (DateTime?)conf.EndTime 实现的 throw 。无需检查conf对于 null与 LINQ to Objects 中一样,因为 LINQ to Entities/SQL 也能自然地处理该问题(只要接收者变量可为空)。

关于c# - 如何让EF高效调用聚合函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51612630/

相关文章:

c# - 首先附加现有数据库 Entity Framework 代码时出错

c# - 有没有办法将 IReadOnlyCollection<T>/IReadOnlyList<T> 与 protobuf-net 一起使用

c# - String.Equals() 没有按预期工作

sql - 需要根据 SQL 中的 Number 列乘以行

SQL Server 多值列问题

SQL根据列号将一行拆分为多行

c# - 使用 EF 6 克隆实体,空引用异常

mysql - 尝试添加新的数据库连接时出现错误 "Could not load file or assembly MySql.data"

c# - 异步运行时方法调用

c# - 将 Index() 从 NEST 分派(dispatch)到 Elasticsearch.NET 失败