我有以下查询。
var query = _context.QuestOrders.Include(a => a.Driver).ThenInclude(i => i.DlStateProvince)
.Where(p => carrierIds.Contains(p.Driver.CarrierId))
....
;
然后尝试调用以下内容:
var queryDto = query.AsNoTracking().ProjectTo<DcReportDonorResultDto>(_mapperConfiguration);
var reports = new PagedList<DcReportDonorResultDto>(queryDto, pageIndex, pageSize);
其中 DcReportDonorResultDto
有一个属性。
public string PrimaryId { get; set; }
它映射了以下内容:
CreateMap<QuestOrder, DcReportDonorResultDto>()
.ForMember(destinationMember => destinationMember.PrimaryId, opt => opt.MapFrom(src => src.Driver.PrimaryId))
和 PrimaryId
定义为 QuestOrder
作为。
public string PrimaryId
{
get
{
if (!string.IsNullOrWhiteSpace(DlNumber) && DlStateProvinceId.HasValue)
return DlStateProvince.Abbreviation + DlNumber.Replace("-", "");
else
return string.Empty;
}
}
我收到以下错误:
System.InvalidOperationException: 'Error generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: 尝试在类型为''的分离实体上懒加载导航属性'DlStateProvince'。对于分离的实体或使用'AsNoTracking()'加载的实体,不支持懒加载。 可以通过将事件ID'CoreEventId.DetachedLazyLoadingWarning'传递给'DbContext.OnConfiguring'或'AddDbContext'中的'ConfigureWarnings'方法来抑制或记录这个异常。
如何解决这个问题?
这个问题是由 计算的 QuestOrder.PrimaryId
属性。
当在LINQ to Entities查询中使用时,这类属性无法转化为SQL,需要客户端评估。而且即使支持,客户端评估在访问里面的导航属性时也不能很好的发挥--无论是急切加载还是懒惰加载都不能正常工作,会导致运行时异常或者错误的返回值。
所以最好的办法是让它们可翻译,这就需要处理可翻译的 表情.
在所有情况下,首先将计算出的属性体从块转换为条件操作符(使其可翻译)。
public string PrimaryId =>
!string.IsNullOrWhiteSpace(this.DlNumber) && this.DlStateProvinceId.HasValue) ?
this.DlStateProvince.Abbreviation + this.DlNumber.Replace("-", "") :
string.Empty;
现在,简短的转折,快速而简单的解决方案是提取计算属性的实际表达式,将其复制粘贴到映射中,并替换为: this
与 src.Driver
:
.ForMember(dst => dst.PrimaryId, opt => opt.MapFrom(src =>
//src.Driver.PrimaryId
!string.IsNullOrWhiteSpace(src.Driver.DlNumber) && src.Driver.DlStateProvinceId.HasValue) ?
src.Driver.DlStateProvince.Abbreviation + src.Driver.DlNumber.Replace("-", "") :
string.Empty
))
长期以来,或者你有很多这样的属性,或者你需要在其他的mappingsqueries中使用它,或者只是因为代码重复,这不是一个好的解决方案。你需要一种方法来替换查询表达式树里面的计算属性访问器,用从正文中提取的相应表达式来替换。
无论是C#,还是BCL或EF Core在这方面都没有帮助。有几个第三方包正在尝试在某种程度上解决这个问题----。LinqKit, NeinLinq 等,但有一个不太为人所知的小宝贝,叫 语音识别器(DeproateDecompiler 它只需修改最少的代码即可实现。
您所需要的只是安装 语音识别器(DeproateDecompiler 或 DelegateDecompiler.EntityFrameworkCore. 包,在计算的属性上用 [Computed]
属性
[Computed] // <--
public string PrimaryId =>
!string.IsNullOrWhiteSpace(this.DlNumber) && this.DlStateProvinceId.HasValue) ?
this.DlStateProvince.Abbreviation + this.DlNumber.Replace("-", "") :
string.Empty;
然后叫 Decompile
(或 DecompileAsync
)在顶层可查询的
var queryDto = query.AsNoTracking()
.ProjectTo<DcReportDonorResultDto>(_mapperConfiguration)
.Decompile(); // <--
AutoMapper不需要特殊的映射,例如,您可以保留通常的
.ForMember(dst => dst.PrimaryId, opt => opt.MapFrom(src => src.Driver.PrimaryId)
对于 AutoMapper 投影查询(使用 ProjectTo
),你甚至可以不需要调用 Decompile
DecompileAsync
通过提供以下两个库之间的小`"桥梁"。
namespace AutoMapper
{
using DelegateDecompiler;
using QueryableExtensions;
public static class AutoMapperExtensions
{
public static IMapperConfigurationExpression UseDecompiler(this IMapperConfigurationExpression config)
{
var resultConverters = config.Advanced.QueryableResultConverters;
for (int i = 0; i < resultConverters.Count; i++)
{
if (!(resultConverters[i] is ExpressionResultDecompiler))
resultConverters[i] = new ExpressionResultDecompiler(resultConverters[i]);
}
return config;
}
class ExpressionResultDecompiler : IExpressionResultConverter
{
IExpressionResultConverter baseConverter;
public ExpressionResultDecompiler(IExpressionResultConverter baseConverter) => this.baseConverter = baseConverter;
public bool CanGetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, PropertyMap propertyMap) => baseConverter.CanGetExpressionResolutionResult(expressionResolutionResult, propertyMap);
public bool CanGetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, ConstructorParameterMap propertyMap) => baseConverter.CanGetExpressionResolutionResult(expressionResolutionResult, propertyMap);
public ExpressionResolutionResult GetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, PropertyMap propertyMap, LetPropertyMaps letPropertyMaps) => Decompile(baseConverter.GetExpressionResolutionResult(expressionResolutionResult, propertyMap, letPropertyMaps));
public ExpressionResolutionResult GetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, ConstructorParameterMap propertyMap) => Decompile(baseConverter.GetExpressionResolutionResult(expressionResolutionResult, propertyMap));
static ExpressionResolutionResult Decompile(ExpressionResolutionResult result)
{
var decompiled = DecompileExpressionVisitor.Decompile(result.ResolutionExpression);
if (decompiled != result.ResolutionExpression)
result = new ExpressionResolutionResult(decompiled, result.Type);
return result;
}
}
}
}
然后直接调用 UseDecompiler()
在AutoMapper初始化过程中,如
var mapperConfig = new MapperConfiguration(config =>
{
config.UseDecompiler(); // <--
// the rest (add profiles, create maps etc.) ...
});