gpt4 book ai didi

c# - Automapper ProjectTo 将 ToList 添加到子属性中

转载 作者:行者123 更新时间:2023-12-03 01:49:54 27 4
gpt4 key购买 nike

我使用 Entity Framework Core 通过投影将实体类映射到 DTO。但是,投影将 ToList 添加到子集合属性中,这会大大减慢查询速度。

公司实体:

public class Company
{
public Company()
{
Employees = new List<CompanyEmployee>();
}

public string Address { get; set; }
public virtual ICollection<CompanyEmployee> Employees { get; set; }
...
}

公司 DTO:

public class CompanyDTO
{
public CompanyDTO()
{
CompanyEmployees = new List<EmployeeDTO>();
}

public string Address { get; set; }
public List<EmployeeDTO> CompanyEmployees { get; set; }
...
}

配置:

CreateMap<Company, CompanyDTO>()
.ForMember(c => c.CompanyEmployees, a => a.MapFrom(src => src.Employees));
CreateMap<CompanyEmployee, EmployeeDTO>();

查询:

UnitOfWork.Repository<Company>()
.ProjectTo<CompanyDTO>(AutoMapper.Mapper.Configuration)
.Take(10)
.ToList();

ProjectTo 之后使用 Expression 属性检查生成的查询会产生以下结果:

Company.AsNoTracking()
.Select(dtoCompany => new CompanyDTO()
{
Address = dtoCompany.Address,
...
CompanyEmployees = dtoCompany.Employees.Select(dtoCompanyEmployee => new EmployeeDTO()
{
CreatedDate = dtoCompanyEmployee.CreatedDate,
...
}).ToList() // WHY??????
})

那个 ToList 调用会导致为每个实体运行选择查询,这不是我想要的,正如您所猜测的那样。我在没有 ToList 的情况下测试了查询(通过手动复制表达式并运行它),一切都按预期工作。如何防止 AutoMapper 添加该调用?我尝试将 DTO 中的 List 类型更改为 IEnumerable 但没有任何改变..

最佳答案

让我们忽略 ToList 对 EF Core 的影响调用关注AutoMapper ProjectTo .

该行为被硬编码在 EnumerableExpressionBinder 中类:

expression = Expression.Call(typeof(Enumerable), propertyMap.DestinationPropertyType.IsArray ? "ToArray" : "ToList", new[] { destinationListType }, expression);

此类属于 AutoMapper QueryableExtensions 处理管道的一部分,负责将源可枚举类型转换为目标可枚举类型。正如我们所看到的,它总是发出 ToArrayToList .

事实上,当目标成员类型为 ICollection<T> 时或IList<T>ToList需要调用,否则表达式将无法编译。但是当目标成员类型为 IEnumerable<T> 时,这是任意的。

因此,如果您想摆脱上述场景中的这种行为,您可以注入(inject)自定义 IExpressionBinder EnumerableExpressionBinder之前(绑定(bind)器按顺序调用,直到 IsMatch 返回 true )像这样(

namespace AutoMapper
{
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using AutoMapper.Configuration.Internal;
using AutoMapper.Mappers.Internal;
using AutoMapper.QueryableExtensions;
using AutoMapper.QueryableExtensions.Impl;

public class GenericEnumerableExpressionBinder : IExpressionBinder
{
public bool IsMatch(PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionResolutionResult result) =>
propertyMap.DestinationPropertyType.IsGenericType &&
propertyMap.DestinationPropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
PrimitiveHelper.IsEnumerableType(propertyMap.SourceType);

public MemberAssignment Build(IConfigurationProvider configuration, PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary<ExpressionRequest, int> typePairCount, LetPropertyMaps letPropertyMaps)
=> BindEnumerableExpression(configuration, propertyMap, request, result, typePairCount, letPropertyMaps);

private static MemberAssignment BindEnumerableExpression(IConfigurationProvider configuration, PropertyMap propertyMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary<ExpressionRequest, int> typePairCount, LetPropertyMaps letPropertyMaps)
{
var expression = result.ResolutionExpression;

if (propertyMap.DestinationPropertyType != expression.Type)
{
var destinationListType = ElementTypeHelper.GetElementType(propertyMap.DestinationPropertyType);
var sourceListType = ElementTypeHelper.GetElementType(propertyMap.SourceType);
var listTypePair = new ExpressionRequest(sourceListType, destinationListType, request.MembersToExpand, request);
var transformedExpressions = configuration.ExpressionBuilder.CreateMapExpression(listTypePair, typePairCount, letPropertyMaps.New());
if (transformedExpressions == null) return null;
expression = transformedExpressions.Aggregate(expression, (source, lambda) => Select(source, lambda));
}

return Expression.Bind(propertyMap.DestinationProperty, expression);
}

private static Expression Select(Expression source, LambdaExpression lambda)
{
return Expression.Call(typeof(Enumerable), "Select", new[] { lambda.Parameters[0].Type, lambda.ReturnType }, source, lambda);
}

public static void InsertTo(List<IExpressionBinder> binders) =>
binders.Insert(binders.FindIndex(b => b is EnumerableExpressionBinder), new GenericEnumerableExpressionBinder());
}
}

它基本上是 EnumerableExpressionBinder 的修改副本与不同IsMatch检查并删除ToList调用发出代码。

现在,如果您将其注入(inject)到 AutoMapper 配置中:

Mapper.Initialize(cfg =>
{
GenericEnumerableExpressionBinder.InsertTo(cfg.Advanced.QueryableBinders);
// ...
});

并将您的 DTO 集合类型设为 IEnumerable<T> :

public IEnumerable<EmployeeDTO> CompanyEmployees { get; set; }

ProjectTo将生成表达式 Select但没有 ToList .

关于c# - Automapper ProjectTo 将 ToList 添加到子属性中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52218340/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com