使用AutoMapper ProjectTo映射可空decimal到自定义类遇空值报错求助
问题:AutoMapper ProjectTo处理可空decimal到自定义类的Null映射异常
我正在使用AutoMapper通过ProjectTo<>()将Entity Framework的数据库实体映射到业务对象。想要将可空decimal类型自动映射到自定义类类型(比如Density),因此使用了自定义类型转换器,但当decimal?属性为null时,会抛出“Null TypeMapping in Sql Tree”异常,且转换器的Convert()方法完全不会被调用。相关代码如下:
基础类及派生类示例
public abstract class BaseUnit { public decimal? DefaultValue { get; protected set; } public abstract ViewableUnit GetRequestedUnit(Unit unit); } public class Density : BaseUnit { public Density() { this.DefaultValue = decimal.Zero; } public Density(decimal? densityInGperL) { this.DefaultValue = densityInGperL; } public decimal? GramsPerLiter { get => this.DefaultValue; set => this.DefaultValue = value; } public decimal? GramsPerDeciliter { get => this.DefaultValue * 10; set => this.DefaultValue = value * 0.1m; } public override ViewableUnit GetRequestedUnit(Unit unit) { // 实现逻辑 } }
转换器及注册代码
public class DecimalUnitTypeConverter : ITypeConverter<decimal?, Density> { public Density Convert(decimal? source, Density destination, ResolutionContext context) { return source.HasValue ? new Density(source.Value) : null; } } Mapper.CreateMap(typeof(decimal?), typeof(Density)).ConvertUsing(typeof(DecimalUnitTypeConverter));
数据库实体及DTO
public class LabResult { public decimal? NumberResult { get; set; } // 其他属性 } public class LabResultDto { public Density NumberResult { get; set; } public void CreateMappings(Profile configuration) { configuration.CreateMap<LabResult, LabResultDto>() .ForMember( dst => dst.NumberResult, opt => { opt.PreCondition(src => src.NumberResult.HasValue); // 有无此行结果一致 opt.MapFrom(src => src.NumberResult); }); } }
最终使用代码
dbContext.LabResults.AsNoTracking() .Where(lab => lab.Id == request.Id) .ProjectTo<LabResultDto>(this.configurationProvider) .ToList();
解决方案
核心原因
AutoMapper的ProjectTo<>()是基于LINQ to SQL的投影机制,会把映射逻辑转换成SQL表达式交给EF执行。而ITypeConverter是内存中执行的转换器,无法被EF的查询提供者识别,当源值为null时,EF无法生成对应的SQL来构造自定义类,因此抛出异常。
解决方法1:使用AutoMapper投影配置(推荐)
用ProjectUsing代替ITypeConverter,它能将映射逻辑转换成EF可识别的SQL表达式:
// 在Profile中配置decimal?到Density的投影 configuration.CreateMap<decimal?, Density>() .ProjectUsing(src => src.HasValue ? new Density(src.Value) : null);
同时简化LabResult到LabResultDto的映射,无需PreCondition:
public void CreateMappings(Profile configuration) { configuration.CreateMap<LabResult, LabResultDto>() .ForMember(dst => dst.NumberResult, opt => opt.MapFrom(src => src.NumberResult)); }
解决方法2:先加载到内存再映射(适合复杂逻辑)
如果自定义类的构造/属性逻辑无法转换成SQL(比如复杂计算),可以先把数据加载到内存,再用AutoMapper的Map()方法触发类型转换器:
dbContext.LabResults.AsNoTracking() .Where(lab => lab.Id == request.Id) .ToList() // 先将数据加载到内存 .Select(lab => mapper.Map<LabResultDto>(lab)) .ToList();
注意:这种方式会把符合条件的所有数据加载到内存,数据量大时需权衡性能。
内容的提问来源于stack exchange,提问作者Peter Grodin
相关产品推荐
相关产品推荐

