AutoMapper:无法直接映射到子对象目标字段

时间:2019-03-25 19:50:11

标签: c# entity-framework-core automapper

我看到AutoMapper有一些奇怪的行为,当目标字段位于子对象中时,我无法直接映射源字段和目标字段。相反,我需要将源字段包装在方法调用中,该方法调用将检查该字段是否为null。如果不为null,则返回该值,否则返回null。似乎没有必要这样做。特别是因为映射到根对象上的目标字段就可以工作,而无需这种技巧。

为了公平起见,我不确定AutoMapper的问题。问题可能出在EntityFramework Core。但是,从表面上看,它看起来像是一个AutoMapper问题。

由于对知识产权的担忧,如果发现问题,我无法分享代码。因此,我编写了一个工作示例,该示例尽可能接近原始代码,并且存在相同的问题。可以在https://github.com/BurikkuDeibu/BrickApi上找到。 master 分支具有我认为应该的代码。 UseMagicMethods 分支具有使事情正常运行所需的代码。 UseMagicMethods 分支中感兴趣的真实文件是https://github.com/BurikkuDeibu/BrickApi/blob/UseMagicMethods/src/WebApi/Models/ElementDetailsMapper.cs

从主分支(抛出异常):

    public class ElementDetailsMapper
    {
        public class ElementDetailsProfile : Profile
        {
            public ElementDetailsProfile()
            {
                CreateMap<ElementDetailEntity, RGBDetail>()
                    .ForMember(dest => dest.R, opts => opts.MapFrom(src => src.Red))
                    .ForMember(dest => dest.G, opts => opts.MapFrom(src => src.Green))
                    .ForMember(dest => dest.B, opts => opts.MapFrom(src => src.Blue));

                CreateMap<ElementDetailEntity, ColorDetail>()
                    .ForMember(dest => dest.RGB, opts => opts.MapFrom(src => src))
                    .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ColorId))
                    .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Color))
                    .ForMember(dest => dest.IsTranparent, opts => opts.MapFrom(src => src.Transparent))
                    .ForMember(dest => dest.IsMetaliic, opts => opts.MapFrom(src => src.Metallic));

                CreateMap<ElementDetailEntity, DesignDetail>()
                    .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DesignId))
                    .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Design));

                CreateMap<ElementDetailEntity, ElementDetails>()
                    .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src))
                    .ForMember(dest => dest.Design, opts => opts.MapFrom(src => src))
                    .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id))
                    .ForMember(dest => dest.ManufactureStartDate, opts => opts.MapFrom(src => src.ManufactureStartDate))
                    .ForMember(dest => dest.ManufactureEndDate, opts => opts.MapFrom(src => src.ManufactureEndDate));
            }
        }
    }

从UseMagicMethods分支(有效):

        public class ElementDetailsProfile : Profile
        {
            public ElementDetailsProfile()
            {
                CreateMap<ElementDetailEntity, RGBDetail>()
                    .ForMember(dest => dest.R, opts => opts.MapFrom(src => ByteMagic(src.Red)))
                    .ForMember(dest => dest.G, opts => opts.MapFrom(src => ByteMagic(src.Green)))
                    .ForMember(dest => dest.B, opts => opts.MapFrom(src => ByteMagic(src.Blue)));

                CreateMap<ElementDetailEntity, ColorDetail>()
                    .ForMember(dest => dest.RGB, opts => opts.MapFrom(src => src))
                    .ForMember(dest => dest.Id, opts => opts.MapFrom(src => ShortMagic(src.ColorId)))
                    .ForMember(dest => dest.Name, opts => opts.MapFrom(src => StringMagic(src.Color)))
                    .ForMember(dest => dest.IsTranparent, opts => opts.MapFrom(src => BooleanMagic(src.Transparent)))
                    .ForMember(dest => dest.IsMetaliic, opts => opts.MapFrom(src => BooleanMagic(src.Metallic)));

                CreateMap<ElementDetailEntity, DesignDetail>()
                    .ForMember(dest => dest.Id, opts => opts.MapFrom(src => StringMagic(src.DesignId)))
                    .ForMember(dest => dest.Name, opts => opts.MapFrom(src => StringMagic(src.Design)));

                CreateMap<ElementDetailEntity, ElementDetails>()
                    .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src))
                    .ForMember(dest => dest.Design, opts => opts.MapFrom(src => src))
                    .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id))
                    .ForMember(dest => dest.ManufactureStartDate, opts => opts.MapFrom(src => src.ManufactureStartDate))
                    .ForMember(dest => dest.ManufactureEndDate, opts => opts.MapFrom(src => src.ManufactureEndDate));
            }

            public static bool? BooleanMagic(bool? input)
            {
                return input.HasValue ? input.Value : (bool?)null;
            }

            public static byte? ByteMagic(byte? input)
            {
                return input.HasValue ? input.Value : (byte?)null;
            }

            public static short? ShortMagic(short? input)
            {
                return input.HasValue ? input.Value : (short?)null;
            }

            public static string StringMagic(string input)
            {
                return input ?? null;
            }
        }
    }

您会注意到,在 UseMagicMethods 分支的该代码文件中,我在方法调用中包装了每个映射到目标子对象字段的源字段。目标字段和源字段的数据类型完全匹配,因此我认为我可以直接映射它们。但是,如果我尝试使用以下堆栈跟踪获取NullReference异常:

   at Microsoft.EntityFrameworkCore.Storage.TypedRelationalValueBufferFactoryFactory.CacheKey.<>c.<GetHashCode>b__6_0(Int32 t, TypeMaterializationInfo v)
   at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
   at Microsoft.EntityFrameworkCore.Storage.TypedRelationalValueBufferFactoryFactory.CacheKey.GetHashCode()
   at System.Collections.Generic.ObjectEqualityComparer`1.GetHashCode(T obj)
   at System.Collections.Concurrent.ConcurrentDictionary`2.TryGetValue(TKey key, TValue& value)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.Storage.TypedRelationalValueBufferFactoryFactory.Create(IReadOnlyList`1 types)
   at Microsoft.EntityFrameworkCore.Query.Sql.Internal.FromSqlNonComposedQuerySqlGenerator.CreateValueBufferFactory(IRelationalValueBufferFactoryFactory relationalValueBufferFactoryFactory, DbDataReader dataReader)
   at Microsoft.EntityFrameworkCore.Query.Internal.ShaperCommandContext.<NotifyReaderCreated>b__14_0(FactoryAndReader s)
   at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.Query.Internal.ShaperCommandContext.NotifyReaderCreated(DbDataReader dataReader)
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.<BufferlessMoveNext>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.<ExecuteAsync>d__7`2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.<MoveNext>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.<MoveNext>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Linq.AsyncEnumerable.<Aggregate_>d__6`3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at WebApi.Controllers.ElementController.<Get>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.<Execute>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextResourceFilter>d__23.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeFilterPipelineAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeAsync>d__16.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.<Invoke>d__7.MoveNext()

您会注意到在堆栈中跟踪了对EntityFramework Core的各种引用,这就是为什么我想知道问题是否真的在那儿的原因。

那么,我是在做错什么,还是AutoMapper或EntityFramework Core存在问题?

1 个答案:

答案 0 :(得分:0)

简短的回答是,因为我使用的是 ProjectTo 方法,所以在翻译查询方面,我依赖于EntityFramework Core支持的内容。目前,它不支持我的尝试。

...

根据上面的评论,我尝试了一些事情。

我尝试从 ProjectTo 切换到 Map ,这似乎效果很好。这是我们决定使用的方法,因此可以消除 magic 方法。

我也尝试使用视图而不是存储过程。这也可以,但是这使我们的DBA非常不满意。

我还尝试对存储过程结果使用查询类型而不是实体类型。这没什么区别。没有 magic 方法,我仍然会得到NullReference异常。