Automapper ProjectTo<> 不使用 Count()



我有一个奇怪的问题与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.1AutoMapper 10.1.1测试了您在.NET Core 3.1中的代码。和- - - - - -

  1. 您的第一个查询生成LEFT JOIN,而不是像您发布的INNER JOIN。因此,该查询的结果不会排除StatusId为空的任何客户。并且,生成的SQL与ProjectTo<>和手动EF投影相同。我建议再次检查您的查询和生成的SQL以确保。

  2. 你的第二个查询生成相同的SQL, SQL你已经发布,与ProjectTo<>和手动EF投影。

解决方案:
如果我没理解错的话,你是想得到-

  1. 指定范围内与Status有关联的Customer的列表
  2. 数据库中所有此类客户的计数。

试试下面的

  1. Customer模型中添加一个可为空的外键属性-
public Guid? StatusId { get; set; }

这将有助于简化查询及其生成的SQL。

  1. 要获得您期望的列表,将第一个查询修改为-
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
  1. 要获得预期的计数,将第二个查询修改为-
var count = Db.Customers
.Where(p => p.StatusId != null)
.Count();

将生成以下SQL -

SELECT COUNT(*)
FROM [Customers] AS [c]
WHERE [c].[StatusId] IS NOT NULL

相关内容

  • 没有找到相关文章

最新更新