У меня довольно простая настройка AutoMapper, в которой я хочу сопоставить IDictionary<Guid, From>
с Dictionary<Guid, To>
. Это отлично работает с IMapper.Map
, но мне действительно хочется использовать IQueryable.ProjectTo
. Однако это приводит к следующему сбою теста:
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Xunit;
public class AutoMapperTests()
{
private readonly IMapper mapper = new MapperConfiguration(
(cfg) =>
{
cfg.CreateMap<From, To>();
}
).CreateMapper();
[Fact]
public void AutoMapperConfigurationIsValid() =>
mapper.ConfigurationProvider.AssertConfigurationIsValid();
[Fact]
public void CanMapFromDictToDict()
{
var key = Guid.NewGuid();
var fromDict = CreateFromDict(key, key.ToString());
var toDict = new[] { fromDict }
.AsEnumerable()
.Select(it => mapper.Map<IDictionary<Guid, To>>(fromDict))
.Single();
Assert.Single(toDict);
Assert.True(toDict.ContainsKey(key));
Assert.True(toDict[key].Id == key.ToString());
}
[Fact]
public void CanProjectFromDictToDict()
{
var key = Guid.NewGuid();
var fromDict = CreateFromDict(key, key.ToString());
var toDict = new[] { fromDict }
.AsQueryable()
.ProjectTo<IDictionary<Guid, To>>(mapper.ConfigurationProvider)
.Single();
Assert.Single(toDict);
Assert.True(toDict.ContainsKey(key));
Assert.True(toDict[key].Id == key.ToString());
}
private IDictionary<Guid, From> CreateFromDict(Guid key, string id) =>
new Dictionary<Guid, From>
{
{
key,
new() { Id = id }
}
};
public class From
{
public required string Id { get; set; }
}
public class To
{
public required string Id { get; set; }
}
}
[xUnit.net 00:00:00.15] AutoMapperTests.CanProjectFromDictToDict [FAIL]
Failed AutoMapperTests.CanProjectFromDictToDict [7 ms]
Error Message:
System.InvalidOperationException : Missing map from System.Collections.Generic.IDictionary`2[System.Guid,AutoMapperTests+From] to System.Collections.Generic.IDictionary`2[System.Guid,AutoMapperTests+To]. Create using CreateMap<IDictionary`2, IDictionary`2>.
Stack Trace:
at AutoMapper.QueryableExtensions.Impl.ProjectionBuilder.PolymorphicMaps(ProjectionRequest& request)
at AutoMapper.QueryableExtensions.Impl.ProjectionBuilder.CreateProjection(ProjectionRequest request)
at AutoMapper.Internal.LockingConcurrentDictionary`2.<>c__DisplayClass2_1.<.ctor>b__1()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at System.Lazy`1.get_Value()
at AutoMapper.Internal.LockingConcurrentDictionary`2.GetOrAdd(TKey& key)
at AutoMapper.QueryableExtensions.Impl.ProjectionBuilder.GetProjection(Type sourceType, Type destinationType, Object parameters, MemberPath[] membersToExpand)
at AutoMapper.QueryableExtensions.Extensions.ToCore(IQueryable source, Type destinationType, IConfigurationProvider configuration, Object parameters, IEnumerable`1 memberPathsToExpand)
at AutoMapper.QueryableExtensions.Extensions.ToCore[TResult](IQueryable source, IConfigurationProvider configuration, Object parameters, IEnumerable`1 memberPathsToExpand)
at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Object parameters, Expression`1[] membersToExpand)
at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Expression`1[] membersToExpand)
at AutoMapperTests.CanProjectFromDictToDict() in /<redacted>/AutoMapperTests.cs:line 43
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Failed! - Failed: 1, Passed: 2, Skipped: 0, Total: 3, Duration: 21 ms
Добавление запрошенного сопоставления приводит к сбою всех тестов:
private readonly IMapper mapper = new MapperConfiguration(
(cfg) =>
{
cfg.CreateMap<From, To>();
cfg.CreateMap<IDictionary<Guid, From>, IDictionary<Guid, To>>();
}
).CreateMapper();
[xUnit.net 00:00:00.13] AutoMapperTests.AutoMapperConfigurationIsValid [FAIL]
[xUnit.net 00:00:00.13] AutoMapperTests.CanProjectFromDictToDict [FAIL]
[xUnit.net 00:00:00.13] AutoMapperTests.CanMapFromDictToDict [FAIL]
Failed AutoMapperTests.AutoMapperConfigurationIsValid [1 ms]
Error Message:
System.ArgumentException : Incorrect number of arguments supplied for call to method 'To get_Item(System.Guid)' (Parameter 'property')
Stack Trace:
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(MemberMap memberMap, Expression destination, MemberInfo destinationMember)
at AutoMapper.Execution.TypeMapPlanBuilder.AddPropertyMaps(List`1 actions)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression createDestination)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda()
at AutoMapper.TypeMap.CreateMapperLambda(IGlobalConfiguration configuration)
at AutoMapper.TypeMap.Seal(IGlobalConfiguration configuration)
at AutoMapper.MapperConfiguration.<.ctor>g__Seal|20_0()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at AutoMapper.MapperConfiguration..ctor(Action`1 configure)
at AutoMapperTests..ctor() in /<redacted>/AutoMapperTests.cs:line 10
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
Failed AutoMapperTests.CanProjectFromDictToDict [1 ms]
Error Message:
System.ArgumentException : Incorrect number of arguments supplied for call to method 'To get_Item(System.Guid)' (Parameter 'property')
Stack Trace:
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(MemberMap memberMap, Expression destination, MemberInfo destinationMember)
at AutoMapper.Execution.TypeMapPlanBuilder.AddPropertyMaps(List`1 actions)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression createDestination)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda()
at AutoMapper.TypeMap.CreateMapperLambda(IGlobalConfiguration configuration)
at AutoMapper.TypeMap.Seal(IGlobalConfiguration configuration)
at AutoMapper.MapperConfiguration.<.ctor>g__Seal|20_0()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at AutoMapper.MapperConfiguration..ctor(Action`1 configure)
at AutoMapperTests..ctor() in /<redacted>/AutoMapperTests.cs:line 10
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
Failed AutoMapperTests.CanMapFromDictToDict [1 ms]
Error Message:
System.ArgumentException : Incorrect number of arguments supplied for call to method 'To get_Item(System.Guid)' (Parameter 'property')
Stack Trace:
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(MemberMap memberMap, Expression destination, MemberInfo destinationMember)
at AutoMapper.Execution.TypeMapPlanBuilder.AddPropertyMaps(List`1 actions)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression createDestination)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda()
at AutoMapper.TypeMap.CreateMapperLambda(IGlobalConfiguration configuration)
at AutoMapper.TypeMap.Seal(IGlobalConfiguration configuration)
at AutoMapper.MapperConfiguration.<.ctor>g__Seal|20_0()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at AutoMapper.MapperConfiguration..ctor(Action`1 configure)
at AutoMapperTests..ctor() in /<redacted>/AutoMapperTests.cs:line 10
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
Failed! - Failed: 3, Passed: 0, Skipped: 0, Total: 3, Duration: 2 ms
Как заставить проекцию работать?
РЕДАКТИРОВАТЬ
@LucianBargaoanu спросил, «[...] это вообще возможно в EF Core без оценки клиента?». Поэтому я добавил следующий тестовый пример, который проходит нормально.
[Fact]
public void CanProjectManuallyFromDictToDict()
{
var key = Guid.NewGuid();
var fromDict = CreateFromDict(key, key.ToString());
var toDict = new[] { fromDict }
.AsQueryable()
.Select(it => fromDict.ToDictionary(it => it.Key, it => new To { Id = it.Value.Id }))
.Single();
Assert.Single(toDict);
Assert.True(toDict.ContainsKey(key));
Assert.True(toDict[key].Id == key.ToString());
}
Я так и думал, но не знаю, как это сделать с помощью ConvertUsing
. Мои настоящие типы From
и To
довольно сложны и полиморфны, поэтому мне бы не хотелось снова объявлять полное ручное преобразование просто для словарной проекции. ConstructUsing
обеспечивает ctx.Mapper.Map
, но мне тоже не удалось заставить это работать.
Возможно ли это вообще в EF Core без оценки клиента? ProjectTo
работает с оценкой сервера и никак не учитывает словари. Сначала попробуйте это в запросе LINQ, без AM.
@LucianBargaoanu Я добавил тестовый пример для проверки. Да, похоже, это сработало.
Это не имеет значения, вам нужно выполнить запрос к реальной БД и доказать, что он работает с оценкой сервера. В противном случае просто используйте Map
.
Я получаю System.InvalidOperationException : The LINQ expression 'it => it.Key' could not be translated.
, что кажется немного странной ошибкой, но, возможно, Linq получит наиболее конкретный ответ?
Я так думаю. Что вы можете сделать, так это получить то, что вам нужно, с помощью ProjectTo
, а затем Map
в словарь.
Я пытаюсь спроецировать свои классы сущностей на классы моделей, сохраняя при этом IQueryable
, поэтому я бы предпочел не делать этого, но, думаю, от этого никуда не деться... @LucianBargaoanu Если вы напишете это как ответ, я приму это.
ProjectTo
работает с оценкой сервера и никак не учитывает словари (и судя по всему EF Core их тоже не поддерживает).
Но что вы можете сделать, так это получить то, что вам нужно, с помощью ProjectTo
, а затем Map
в словарь.
Эта карта бесполезна :) Вам нужно реализовать ее с помощью
Convertusing
.