sot*_*otn 4 c# entity-framework automapper entity-framework-core
我使用projection通过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; }
...
}
Run Code Online (Sandbox Code Playgroud)
公司DTO:
public class CompanyDTO
{
public CompanyDTO()
{
CompanyEmployees = new List<EmployeeDTO>();
}
public string Address { get; set; }
public List<EmployeeDTO> CompanyEmployees { get; set; }
...
}
Run Code Online (Sandbox Code Playgroud)
组态:
CreateMap<Company, CompanyDTO>()
.ForMember(c => c.CompanyEmployees, a => a.MapFrom(src => src.Employees));
CreateMap<CompanyEmployee, EmployeeDTO>();
Run Code Online (Sandbox Code Playgroud)
查询:
UnitOfWork.Repository<Company>()
.ProjectTo<CompanyDTO>(AutoMapper.Mapper.Configuration)
.Take(10)
.ToList();
Run Code Online (Sandbox Code Playgroud)
在使用Expression属性检查生成的查询后,将ProjectTo产生以下结果:
Company.AsNoTracking()
.Select(dtoCompany => new CompanyDTO()
{
Address = dtoCompany.Address,
...
CompanyEmployees = dtoCompany.Employees.Select(dtoCompanyEmployee => new EmployeeDTO()
{
CreatedDate = dtoCompanyEmployee.CreatedDate,
...
}).ToList() // WHY??????
})
Run Code Online (Sandbox Code Playgroud)
该ToList调用导致对每个实体运行选择查询,这并不是我所想的。我在没有该查询的情况下测试了该查询ToList(通过手动复制该表达式并运行它),并且一切正常。如何防止AutoMapper添加该呼叫?我尝试将ListDTO中的类型更改为,IEnumerable但没有任何更改。
让我们忽略该ToList调用的EF Core影响,并专注于AutoMapper ProjectTo。
该行为在EnumerableExpressionBinder类中进行了硬编码:
expression = Expression.Call(typeof(Enumerable), propertyMap.DestinationPropertyType.IsArray ? "ToArray" : "ToList", new[] { destinationListType }, expression);
Run Code Online (Sandbox Code Playgroud)
此类是AutoMapper QueryableExtensions处理管道的一部分,负责将可枚举的源转换为可枚举的目标。正如我们所看到的,它总是发出ToArray或ToList。
实际上,当目标成员类型为ICollection<T>或时IList<T>,ToList则需要调用,因为否则该表达式将无法编译。但是,当目标成员类型为时IEnumerable<T>,这是任意的。
所以,如果你想摆脱在上述场景,行为,你可以注入定制IExpressionBinder 之前的EnumerableExpressionBinder(粘合剂被称为为了直到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());
}
}
Run Code Online (Sandbox Code Playgroud)
从本质上来说,这是的修改后的副本,EnumerableExpressionBinder具有不同的IsMatch检查和删除的ToList调用发射代码。
现在,如果将其注入到AutoMapper配置中:
Mapper.Initialize(cfg =>
{
GenericEnumerableExpressionBinder.InsertTo(cfg.Advanced.QueryableBinders);
// ...
});
Run Code Online (Sandbox Code Playgroud)
并设置您的DTO收集类型IEnumerable<T>:
public IEnumerable<EmployeeDTO> CompanyEmployees { get; set; }
Run Code Online (Sandbox Code Playgroud)
在ProjectTo将生成的表达Select,但W / O型ToList。
| 归档时间: |
|
| 查看次数: |
702 次 |
| 最近记录: |