AutoMapper:直接映射到子对象目标字段不能按预期工作

问题描述 投票:3回答:1

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

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

由于对知识产权的担忧,我无法分享代码被发现的问题。所以,我写了一个工作样本,它尽可能接近原始代码,并且展示了同样的问题。它可以在https://github.com/BurikkuDeibu/BrickApi找到。主分支拥有我认为应该的代码。 UseMagicMethods分支具有使工作所需的hack的代码。 UseMagicMethods分支中感兴趣的真实文件是https://github.com/BurikkuDeibu/BrickApi/blob/UseMagicMethods/src/WebApi/Models/ElementDetailsMapper.cs

从master分支(抛出异常):

    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存在问题?

c# entity-framework-core automapper
1个回答
0
投票

简短的回答是,因为我使用的是ProjectTo方法,所以在翻译查询方面,我依赖于EntityFramework Core支持的内容。在这一点上,它不支持我想要做的事情。

...

根据上面的评论,我尝试了一些方法。

我尝试从ProjectTo切换到Map,这看起来效果很好。这是我们决定使用的方法,因此我们可以消除魔术方法。

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

我还尝试使用查询类型而不是实体类型来存储我的存储过程结果。这没有任何区别。没有魔术方法,我仍然会得到NullReference异常。

© www.soinside.com 2019 - 2024. All rights reserved.