我有一个奇怪的问题与AutoMapper
(我使用。net core 3.1和AutoMapper 10.1.1)
我正在做一个简单的项目列表和一个简单的总记录预测计数:
var data = Db.Customers
.Skip((1 - 1) * 25)
.Take(25)
.ProjectTo<CustomerViewModel>(Mapper.ConfigurationProvider)
.ToList();
var count = Db.Customers
.ProjectTo<CustomerViewModel>(Mapper.ConfigurationProvider)
.Count();
第一行创建预期的SQL:
exec sp_executesql N'SELECT [c].[Code], [c].[Id], [c].[Name], [c].[Website], [s].Name
FROM [Customers] AS [c]
INNER JOIN [Status] AS [s] ON [s].id = [c].StatusId
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY',N'@__p_0 int,@__p_1 int',@__p_0=0,@__p_1=25
第二行,Count()。似乎完全忽略了投影:
SELECT COUNT(*)
FROM [Customers] AS [c]
这样做的结果是,任何具有空StatusId
的客户将被排除在第一个查询之外,但在第二个查询中包含在计数中。这会破坏分页。
我本以为这个项目应该创建如下内容:
SELECT COUNT(*)
FROM [Customers] AS [c]
INNER JOIN [Status] AS [s] ON [s].id = [c].StatusId
有人知道为什么Count()忽略了ProjectTo<>
吗?
编辑
执行计划:
值(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable 1 [Domain.Customer]) .Select (dtoCustomer=比;new CustomerViewModel() {Code = dtoCustomer。代码,Id = dtoCustomer。Id, Name = dtoCustomer。名称,StatusName =dtoCustomer.Status。Name, Website = dtoCustomer.Website})
编辑2021/02/19
映射计划:
EF实体-
public class Customer
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Code { get; private set; }
public string Website { get; private set; }
public CustomerStatus Status { get; private set; }
public Customer() { }
}
public class CustomerStatus
{
public Guid Id { get; private set; }
public string Name { get; private set; }
}
ViewModel——
public class CustomerViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string Website { get; set; }
public string StatusName { get; set; }
}
——映射
CreateMap<Customer, CustomerViewModel>();
编辑2021/02/20 -手动排除状态
正如@atiyar回答中指出的那样,您可以手动排除状态。这让我很不爽。我的理由是:
如果您执行这个查询,作为最根查询:
Db.Customers.ProjectTo<CustomerViewModel>(_mapper.ConfigurationProvider)
你:
exec sp_executesql N'SELECT TOP(@__p_0) [c].[Id], [c].[Name], [c0].[Name]
AS [StatusName]
FROM [Customers] AS [c]
INNER JOIN [CustomerStatus] AS [c0] ON [c].[StatusId] = [c0].[Id]',N'@__p_0
int',@__p_0=5
这表明automapper理解并且可以看到状态和客户之间存在必要的关系。但是当你应用计数机制时:
Db.Customers.ProjectTo<CustomerViewModel>(_mapper.ConfigurationProvider).Count()
突然之间,状态和客户之间的关系消失了。
SELECT COUNT(*)
FROM [Customers] AS [c]
根据我使用Linq的经验,每个查询步骤都以可预测的方式修改前一步。我希望计数建立在第一个命令的基础上,并将计数作为其中的一部分。
有趣的是,如果你执行这个:_context.Customers.ProjectTo<CustomerViewModel>(_mapper.ConfigurationProvider).Take(int.MaxValue).Count()
Automapper应用这个关系,结果是我所期望的:
exec sp_executesql N'SELECT COUNT(*)
FROM (
SELECT TOP(@__p_0) [c].[Id], [c].[Name], [c0].[Name] AS [Name0], [c0].[Id]
AS [Id0]
FROM [Customers] AS [c]
INNER JOIN [CustomerStatus] AS [c0] ON [c].[StatusId] = [c0].[Id]
) AS [t]',N'@__p_0 int',@__p_0=2147483647
编辑2021/02/20 -最新版本
似乎在最新版本中行为相同。
仅供参考:我们有一个场景,记录是定期从另一个应用程序导入的。我们希望使用内部连接来排除在另一个表中没有匹配记录的记录。然后这些记录将在稍后由导入过程更新。
但是从应用程序的角度来看,它应该总是忽略这些记录,因此内部连接和状态是强制性的。但是我们必须手动排除它们(按照atiyar的解决方案),使用where来防止分页返回夸大的页面计数。
编辑2021/02/20 -进一步挖掘这似乎是EF团队的设计选择和优化。这里的假设是,如果关系是非空的。那么连接就不会被包含在性能提升中。解决这个问题的方法是@atiyar提出的。谢谢大家的帮助@atiyar &@Lucian-Bargaoanu .
我已经用Entity Framework Core 3.1
和AutoMapper 10.1.1
测试了您在.NET Core 3.1
中的代码。和- - - - - -
-
您的第一个查询生成
LEFT JOIN
,而不是像您发布的INNER JOIN
。因此,该查询的结果不会排除StatusId
为空的任何客户。并且,生成的SQL与ProjectTo<>
和手动EF投影相同。我建议再次检查您的查询和生成的SQL以确保。 -
你的第二个查询生成相同的SQL, SQL你已经发布,与
ProjectTo<>
和手动EF投影。
解决方案:
如果我没理解错的话,你是想得到-
- 指定范围内与
Status
有关联的Customer
的列表 - 数据库中所有此类客户的计数。
试试下面的
- 在
Customer
模型中添加一个可为空的外键属性-
public Guid? StatusId { get; set; }
这将有助于简化查询及其生成的SQL。
- 要获得您期望的列表,将第一个查询修改为-
var viewModels = Db.Customers
.Skip((1 - 1) * 25)
.Take(25)
.Where(p => p.StatusId != null)
.ProjectTo<CustomerViewModel>(_Mapper.ConfigurationProvider)
.ToList();
将生成以下SQL -
exec sp_executesql N'SELECT [t].[Code], [t].[Id], [t].[Name], [s].[Name] AS [StatusName], [t].[Website]
FROM (
SELECT [c].[Id], [c].[Code], [c].[Name], [c].[StatusId], [c].[Website]
FROM [Customers] AS [c]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t]
LEFT JOIN [Statuses] AS [s] ON [t].[StatusId] = [s].[Id]
WHERE [t].[StatusId] IS NOT NULL',N'@__p_0 int,@__p_1 int',@__p_0=0,@__p_1=25
- 要获得预期的计数,将第二个查询修改为-
var count = Db.Customers
.Where(p => p.StatusId != null)
.Count();
将生成以下SQL -
SELECT COUNT(*)
FROM [Customers] AS [c]
WHERE [c].[StatusId] IS NOT NULL