简化 Entity Framework 核心查询
Simplify Entity Framework Core query
我不明白如何简化这个请求
var dialogs = await dbContext.UsersDialogs
.AsNoTracking()
.Where(x => x.UserId == userId)
.Select(x => new DialogModel
{
Id = x.DialogId,
Login = x.Dialog.Name,
Image = x.User.FacialImage,
IsConfirm = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().IsRead,
DateTime = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().DateCreate,
LastMessage = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().Content,
LastUserId = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().UserId
})
.ToListAsync();
会转化成这样的请求
SELECT [u].[DialogId] AS [Id], [d].[Name] AS [Login], [u0].[FacialImage] AS [Image], (
SELECT TOP(1) [m].[IsRead]
FROM [Messages] AS [m]
WHERE [d].[Id] = [m].[DialogId]
ORDER BY [m].[DateCreate] DESC) AS [IsConfirm], (
SELECT TOP(1) [m0].[DateCreate]
FROM [Messages] AS [m0]
WHERE [d].[Id] = [m0].[DialogId]
ORDER BY [m0].[DateCreate] DESC) AS [DateTime], (
SELECT TOP(1) [m1].[Content]
FROM [Messages] AS [m1]
WHERE [d].[Id] = [m1].[DialogId]
ORDER BY [m1].[DateCreate] DESC) AS [LastMessage], (
SELECT TOP(1) [m2].[UserId]
FROM [Messages] AS [m2]
WHERE [d].[Id] = [m2].[DialogId]
ORDER BY [m2].[DateCreate] DESC) AS [LastUserId]
FROM [UsersDialogs] AS [u]
INNER JOIN [Dialogs] AS [d] ON [u].[DialogId] = [d].[Id]
INNER JOIN [Users] AS [u0] ON [u].[UserId] = [u0].[Id]
WHERE [u].[UserId] = @__userId_0
我能以某种方式优化它吗?我不想使用 SQL 查询,因为 Linq 对我来说似乎更方便。
您创建的查询直接转换为相同的 SQL。您可以通过附加 Select
来省略重复查询。也不需要 AsNoTracking
- EF Core 不添加自定义实体。
var dialogs = await dbContext.UsersDialogs
.Where(x => x.UserId == userId)
.Select(x => new
{
UserDialog = x,
LastMessage = x.Dialog.Messages.OrderByDescending(x => x.DateCreate).FirstOrDefault()
})
.Select(x => new DialogModel
{
Id = x.UserDialog.DialogId,
Login = x.UserDialog.Dialog.Name,
Image = x.UserDialog.User.FacialImage,
IsConfirm = x.LastMessage.IsRead,
DateTime = x.LastMessage.DateCreate,
LastMessage = x.LastMessage.Content,
LastUserId = x.LastMessage.UserId
})
.ToListAsync();
但上述查询的质量取决于当前 EF Core LINQ 转换器的质量。因此,添加了另一个变体:
var query =
from ud in dbContext.UsersDialogs
from lm in ud.Dialog.Messages.OrderByDescending(x => x.DateCreate)
.Take(1).DefaultIfEmpty()
select new DialogModel
{
Id = ud.DialogId,
Login = ud.Dialog.Name,
Image = ud.User.FacialImage,
IsConfirm = lm.IsRead,
DateTime = lm.DateCreate,
LastMessage = lm.Content,
LastUserId = lm.UserId
};
var dialogs = await query.ToListAsync();
就LINQ查询而言,您可以使用查询语法和let
:
来简化它
from d in dbContext.UsersDialogs
where d.UserId == userId
let lastMessage = d.Dialog.Messages.OrderBy(d => d.DateCreate).LastOrDefault()
select new DialogModel
{
Id = d.DialogId,
Login = d.Dialog.Name,
Image = d.User.FacialImage,
IsConfirm = lastMessage.IsRead,
DateTime = lastMessage.DateCreate,
LastMessage = lastMessage.Content,
LastUserId = lastMessage.UserId
}
但这并没有优化 SQL 查询。 EF 为 Messages table 中的每个字段一遍又一遍地生成相同的子查询。在 SQL 服务器中,查询计划不会将这些子查询优化到一个分支中。
如果您真的想优化 SQL 查询,您必须这样做:
(
from d in dbContext.UsersDialogs
where d.UserId == userId
select new
{
Id = d.DialogId,
Login = d.Dialog.Name,
Image = d.User.FacialImage,
LastMessage = (from d.Dialog.Messages
orderby d.DateCreate
select new
{
IsConfirm = d.IsRead
d.DateCreate,
d.Content,
d.UserId
}).LastOrDefault()
}
).AsEnumerable()
.Select(x => new DialogModel
{
Id = x.DialogId,
Login = x.Dialog.Name,
Image = x.User.FacialImage,
LastMessage.IsConfirm,
LastMessage.DateCreate,
LastMessage.Content,
LastMessage.UserId
})
通过添加 AsEnumerable
,强制对查询的最后部分进行客户端评估,EF 生成一个查询,其中包含一个子查询,该子查询使用 ROW_NUMBER() OVER
函数仅获取最后一条消息一次.但是当然这样做很麻烦。但如果第一个查询遭受相当大的性能损失,则可能有必要。
我不明白如何简化这个请求
var dialogs = await dbContext.UsersDialogs
.AsNoTracking()
.Where(x => x.UserId == userId)
.Select(x => new DialogModel
{
Id = x.DialogId,
Login = x.Dialog.Name,
Image = x.User.FacialImage,
IsConfirm = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().IsRead,
DateTime = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().DateCreate,
LastMessage = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().Content,
LastUserId = x.Dialog.Messages.OrderBy(x => x.DateCreate).LastOrDefault().UserId
})
.ToListAsync();
会转化成这样的请求
SELECT [u].[DialogId] AS [Id], [d].[Name] AS [Login], [u0].[FacialImage] AS [Image], (
SELECT TOP(1) [m].[IsRead]
FROM [Messages] AS [m]
WHERE [d].[Id] = [m].[DialogId]
ORDER BY [m].[DateCreate] DESC) AS [IsConfirm], (
SELECT TOP(1) [m0].[DateCreate]
FROM [Messages] AS [m0]
WHERE [d].[Id] = [m0].[DialogId]
ORDER BY [m0].[DateCreate] DESC) AS [DateTime], (
SELECT TOP(1) [m1].[Content]
FROM [Messages] AS [m1]
WHERE [d].[Id] = [m1].[DialogId]
ORDER BY [m1].[DateCreate] DESC) AS [LastMessage], (
SELECT TOP(1) [m2].[UserId]
FROM [Messages] AS [m2]
WHERE [d].[Id] = [m2].[DialogId]
ORDER BY [m2].[DateCreate] DESC) AS [LastUserId]
FROM [UsersDialogs] AS [u]
INNER JOIN [Dialogs] AS [d] ON [u].[DialogId] = [d].[Id]
INNER JOIN [Users] AS [u0] ON [u].[UserId] = [u0].[Id]
WHERE [u].[UserId] = @__userId_0
我能以某种方式优化它吗?我不想使用 SQL 查询,因为 Linq 对我来说似乎更方便。
您创建的查询直接转换为相同的 SQL。您可以通过附加 Select
来省略重复查询。也不需要 AsNoTracking
- EF Core 不添加自定义实体。
var dialogs = await dbContext.UsersDialogs
.Where(x => x.UserId == userId)
.Select(x => new
{
UserDialog = x,
LastMessage = x.Dialog.Messages.OrderByDescending(x => x.DateCreate).FirstOrDefault()
})
.Select(x => new DialogModel
{
Id = x.UserDialog.DialogId,
Login = x.UserDialog.Dialog.Name,
Image = x.UserDialog.User.FacialImage,
IsConfirm = x.LastMessage.IsRead,
DateTime = x.LastMessage.DateCreate,
LastMessage = x.LastMessage.Content,
LastUserId = x.LastMessage.UserId
})
.ToListAsync();
但上述查询的质量取决于当前 EF Core LINQ 转换器的质量。因此,添加了另一个变体:
var query =
from ud in dbContext.UsersDialogs
from lm in ud.Dialog.Messages.OrderByDescending(x => x.DateCreate)
.Take(1).DefaultIfEmpty()
select new DialogModel
{
Id = ud.DialogId,
Login = ud.Dialog.Name,
Image = ud.User.FacialImage,
IsConfirm = lm.IsRead,
DateTime = lm.DateCreate,
LastMessage = lm.Content,
LastUserId = lm.UserId
};
var dialogs = await query.ToListAsync();
就LINQ查询而言,您可以使用查询语法和let
:
from d in dbContext.UsersDialogs
where d.UserId == userId
let lastMessage = d.Dialog.Messages.OrderBy(d => d.DateCreate).LastOrDefault()
select new DialogModel
{
Id = d.DialogId,
Login = d.Dialog.Name,
Image = d.User.FacialImage,
IsConfirm = lastMessage.IsRead,
DateTime = lastMessage.DateCreate,
LastMessage = lastMessage.Content,
LastUserId = lastMessage.UserId
}
但这并没有优化 SQL 查询。 EF 为 Messages table 中的每个字段一遍又一遍地生成相同的子查询。在 SQL 服务器中,查询计划不会将这些子查询优化到一个分支中。
如果您真的想优化 SQL 查询,您必须这样做:
(
from d in dbContext.UsersDialogs
where d.UserId == userId
select new
{
Id = d.DialogId,
Login = d.Dialog.Name,
Image = d.User.FacialImage,
LastMessage = (from d.Dialog.Messages
orderby d.DateCreate
select new
{
IsConfirm = d.IsRead
d.DateCreate,
d.Content,
d.UserId
}).LastOrDefault()
}
).AsEnumerable()
.Select(x => new DialogModel
{
Id = x.DialogId,
Login = x.Dialog.Name,
Image = x.User.FacialImage,
LastMessage.IsConfirm,
LastMessage.DateCreate,
LastMessage.Content,
LastMessage.UserId
})
通过添加 AsEnumerable
,强制对查询的最后部分进行客户端评估,EF 生成一个查询,其中包含一个子查询,该子查询使用 ROW_NUMBER() OVER
函数仅获取最后一条消息一次.但是当然这样做很麻烦。但如果第一个查询遭受相当大的性能损失,则可能有必要。