EF 核心:分离的延迟加载导航属性



我有以下查询:

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))

PrimaryIdQuestOrder中定义为:

public string PrimaryId
{
get
{
if (!string.IsNullOrWhiteSpace(DlNumber) && DlStateProvinceId.HasValue)
return DlStateProvince.Abbreviation + DlNumber.Replace("-", "");
else
return string.Empty;
}
}

我收到以下错误:

系统无效操作异常:"为警告生成错误 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadWarning: 尝试延迟加载导航属性"DlStateProvince" 在类型为 '' 的分离实体上。不支持延迟加载 分离的实体或加载了"AsNoTracking(("的实体。 可以通过传递事件 ID 来抑制或记录此异常 "CoreEventId.DetachedLazyLoadWarning"到"ConfigureWarnings" 方法在"DbContext.OnConfiguring"或"AddDbContext"中。

如何解决这个问题?

该问题是由计算的QuestOrder.PrimaryId属性引起的。

在 LINQ to 实体查询中使用时,此类属性无法转换为 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
))

从长远来看,或者如果您有许多这样的属性,或者您需要在其他映射/查询中使用它,或者只是因为代码重复,这不是一个好的解决方案。您需要一种方法将查询表达式树中的计算属性访问器替换为从正文中提取的相应表达式。

C#、BCL 或 EF Core 在这方面都没有帮助。一些第三方软件包正试图在某种程度上解决这个问题 - LinqKit,NeinLinq等,但是有一个不太知名的宝石称为DelegateDecompiler,它以最少的代码更改来做到这一点。

您所需要的只是安装 DelegateDecompiler 或 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()

var mapperConfig = new MapperConfiguration(config =>
{
config.UseDecompiler(); // <--
// the rest (add profiles, create maps etc.) ...
});

最新更新