Давайте проигнорируем влияние EF Core на вызов ToList
и сконцентрируемся на AutoMapper ProjectTo
.
Поведение жестко закодировано в EnumerableExpressionBinder
классе:
expression = Expression.Call(typeof(Enumerable), propertyMap.DestinationPropertyType.IsArray ? "ToArray" : "ToList", new[] { destinationListType }, expression);
Этот класс является частью конвейера обработки 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());
}
}
Это в основном модифицированная копия EnumerableExpressionBinder
с другой проверкой IsMatch
и удаленным ToList
кодом передачи вызова.
Теперь, если вы добавите его в конфигурацию AutoMapper:
Mapper.Initialize(cfg =>
{
GenericEnumerableExpressionBinder.InsertTo(cfg.Advanced.QueryableBinders);
// ...
});
и создайте коллекцию DTO IEnumerable<T>
:
public IEnumerable<EmployeeDTO> CompanyEmployees { get; set; }
ProjectTo
сгенерирует выражение с Select
, но без ToList
.