vic*_*tor 5 c# linq entity-framework
我正在使用EF和进行一些测试Linq to entities,以尝试提高我的应用程序性能。
我只是注意到一些奇怪的事情(对我来说),我无法解释,也无法真正判断是否会产生相当大的开销。
这是我的链接:
var result = from n in query
orderby n.PersonId
select new
{
id = n.Id,
appointmentId = n.AppointmentId,
message = n.Message,
wasRead = n.Read,
canDismiss = (n.Appointment.Status != AppointmentStatus.Waiting),
date = n.IssueDateUtc
};
Run Code Online (Sandbox Code Playgroud)
这是生成的sql:
SELECT
[Project1].[Id] AS [Id],
[Project1].[AppointmentId] AS [AppointmentId],
[Project1].[Message] AS [Message],
[Project1].[Read] AS [Read],
[Project1].[C1] AS [C1],
[Project1].[IssueDateUtc] AS [IssueDateUtc]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Read] AS [Read],
[Extent1].[Message] AS [Message],
[Extent1].[IssueDateUtc] AS [IssueDateUtc],
[Extent1].[AppointmentId] AS [AppointmentId],
[Extent1].[PersonId] AS [PersonId],
CASE WHEN ( NOT ((1 = [Extent2].[Status]) AND ([Extent2].[Status] IS NOT NULL))) THEN cast(1 as bit) WHEN (1 = [Extent2].[Status]) THEN cast(0 as bit) END AS [C1]
FROM [dbo].[Notification] AS [Extent1]
LEFT OUTER JOIN [dbo].[Appointment] AS [Extent2] ON [Extent1].[AppointmentId] = [Extent2].[Id]
WHERE [Extent1].[PersonId] = @p__linq__0
) AS [Project1]
**ORDER BY [Project1].[PersonId] ASC**
Run Code Online (Sandbox Code Playgroud)
我不明白需要将结果分组到另一个投影(Project1)中,而这似乎工作得很好:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Read] AS [Read],
[Extent1].[Message] AS [Message],
[Extent1].[IssueDateUtc] AS [IssueDateUtc],
[Extent1].[AppointmentId] AS [AppointmentId],
[Extent1].[PersonId] AS [PersonId],
CASE WHEN ( NOT ((1 = [Extent2].[Status]) AND ([Extent2].[Status] IS NOT NULL))) THEN cast(1 as bit) WHEN (1 = [Extent2].[Status]) THEN cast(0 as bit) END AS [C1]
FROM [dbo].[Notification] AS [Extent1]
LEFT OUTER JOIN [dbo].[Appointment] AS [Extent2] ON [Extent1].[AppointmentId] = [Extent2].[Id]
WHERE [Extent1].[PersonId] = @p__linq__0
**ORDER BY [Extent1].[PersonId] ASC**
Run Code Online (Sandbox Code Playgroud)
我发现 ef 和 linq 生成了大量有问题的 sql,我开始怀疑只编写原始 sql 是否会更好。
问题是:生成的 sql 额外代码位是否值得担心?为什么需要这个投影?
编辑以添加新的 linq
正如评论中提到的,冗长的内容可能是由后续运行的查询引起的。我重写了 linq 以仅使用一个查询对象,结果仍然相同:
dbSet.Where(n => n.PersonId == id).Select(n => new
{
Id = n.Id,
AppointmentId = n.AppointmentId,
Message = n.Message,
Read = n.Read,
CanBeDismissed = (n.Appointment.Status != AppointmentStatus.Waiting),
IssueDate = n.IssueDateUtc
}).OrderBy(n => n.Id).ToList();
Run Code Online (Sandbox Code Playgroud)
执行计划(两者相同sqls)
编辑2
刚刚通过简单的计数得到了这个查询。
dbSet.Count(x => x.Id == 1 && x.Read == false);
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Notification] AS [Extent1]
WHERE ([Extent1].[PersonId] = 19) AND (0 = [Extent1].[Read])
) AS [GroupBy1]
Run Code Online (Sandbox Code Playgroud)
预期的:
SELECT
COUNT(1) AS [A1]
FROM [dbo].[Notification] AS [Extent1]
WHERE ([Extent1].[PersonId] = 19) AND (0 = [Extent1].[Read])
Run Code Online (Sandbox Code Playgroud)
我不明白所有这些包装纸是从哪里来的以及为什么。
我在我的机器上整理了一个小示例项目。实际上导致第一个示例中的投影的是 CanBeDismissed 字段的条件计算,从而导致CASE WHENSQL。如果您忽略这一点,实体框架将不会进行额外的投影。
因此,通过条件检查:
db.Notifications
.Where(n => n.AppointmentId == 1)
.OrderBy(n => n.Id)
.Select(n => new
{
Id = n.Id,
Message = n.Message,
HasMessage = n.Message != null
}).ToList();
Run Code Online (Sandbox Code Playgroud)
生成的 SQL 是:
SELECT
[Project1].[Id] AS [Id],
[Project1].[Message] AS [Message],
[Project1].[C1] AS [C1]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Message] AS [Message],
CASE WHEN ([Extent1].[Message] IS NOT NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
FROM [dbo].[Notifications] AS [Extent1]
WHERE 1 = [Extent1].[AppointmentId]
) AS [Project1]
ORDER BY [Project1].[Id] ASC
Run Code Online (Sandbox Code Playgroud)
让我添加生成的执行计划以供以后参考:
如果你忽略它:
db.Notifications
.Where(n => n.AppointmentId == 1)
.OrderBy(n => n.Id)
.Select(n => new
{
Id = n.Id,
Message = n.Message
}).ToList();
Run Code Online (Sandbox Code Playgroud)
EF 不进行投影:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Message] AS [Message]
FROM [dbo].[Notifications] AS [Extent1]
WHERE 1 = [Extent1].[AppointmentId]
ORDER BY [Extent1].[Id] ASC
Run Code Online (Sandbox Code Playgroud)
这就是原因。这同样适用于您的count示例:如果发生任何分组,EF 将添加一个额外的投影,这使得查询更加详细。但重要的是,正如对您的问题的评论中所讨论的那样,它不会损害性能,无需担心这个额外的投影。
现在让我通过添加以下查询的执行计划来证明这一点,其中我刚刚从第一个查询中删除了投影并将 orderby 移至内部查询:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Message] AS [Message],
CASE WHEN ([Extent1].[Message] IS NOT NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
FROM [dbo].[Notifications] AS [Extent1]
WHERE 1 = [Extent1].[AppointmentId]
ORDER BY [Extent1].[Id] ASC
Run Code Online (Sandbox Code Playgroud)
完全相同 - 没有添加额外的任务,并且成本分配保持不变。SQL 查询优化器将很好地优化此类预测。
再说一次,不要担心预测——它们不会伤害你,虽然我同意它们看起来而且有时不必要地冗长。但这里有两件事可能对你有帮助:
性能问题:
首先,如果您的查询遇到性能问题,请查看Clustered Index Scan您发布的执行计划中为什么会出现 a 。这并不总是某些索引问题的征兆,但很常见。您的问题可能根源于此。
摆脱不必要的预测:
如果您仍然想在所有(或至少更多)情况下摆脱这些投影,可以使用 Entity Framework Core 1.0 - 它实际上生成比 EF 6 更好的 SQL。可能值得考虑迁移到它,但请注意它不具备 EF 6 的所有功能,因此如果您使用 EF Core 1.0 不提供的功能,它可能不是一个选择。但它可以与完整的 .NET Framework 4.x 配合使用!
以下是当我执行答案的第一个 LINQ 语句时 EF Core 1.0 生成的示例:
SELECT [n].[Id], [n].[Message], CASE
WHEN [n].[Message] IS NULL
THEN CAST(0 AS BIT) ELSE CAST(1 AS BIT)
END
FROM [Notifications] AS [n]
WHERE ([n].[Id] = 1) AND ([n].[Id] = 1)
ORDER BY [n].[Id]
Run Code Online (Sandbox Code Playgroud)