[英]Entity Framework Core Invoke an Expression on Navigation property
試圖為此搜索很多,但無法想出一個有效的答案。 這是我想要做的:
我有一個實體ObjectA
,它有一個導航屬性ObjectB
(不是集合類型,它是一個通過延遲加載加載的虛擬屬性)。 當我對 A 執行 Where 查詢時,我還想使用另一個映射 B 的表達式擴展表達式中的屬性 B。
這里有一些代碼來演示,問題是在 ToObjectADto() 函數中
public static Expression<Func<ObjectB, ObjectBDto>> ToObjectBDto()
{
return b => new ObjectBDto
{
Prop1 = b.Prop1,
Prop2 = b.Prop2;
};
}
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
return a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = /* How can I call the ToObjectBDto Expression here without re-writing it? */
};
}
var aDto = _dbContext.ObjectAs.Where(q => q.SomeProperty > 0).Select(ToObjectADto());
我試圖創建一個編譯的表達式:
private static _toBDtoCompiled = ToObjectBDto().Compile();
然后在ToObjectADto()
調用它,如下所示,但我收到API Error There is already an open DataReader associated
錯誤,因為它是在客戶端執行的。
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
return a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = _toBDto().Invoke(a.ObjectB)
};
}
我的建議是節省自己的工作並利用 AutoMapper。 這里的好處是 Automapper 可以通過ProjectTo
提供 EF 的IQueryable
實現來構建查詢和填充 DTO 圖。
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ObjectA, ObjectADto>();
cfg.CreateMap<ObjectB, ObjectBDto>();
});
var aDto = _dbContext.ObjectAs.Where(q => q.SomeProperty > 0).ProjectTo<ObjectADto>(config);
任何無法在 Object 和 DTO 之間推斷的特定映射都可以在映射中設置。 ProjectTo
與自定義映射的優勢在於,它將構建相關查詢,而不會導致延遲加載命中或觸發 EF 無法轉換為 SQL 的代碼。 (一個查詢來填充所有相關的 DTO)
Automapper 可以幫助將值從 DTO 復制回新實體或更新現有實體:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<NewObjectADto, ObjectA>();
cfg.CreateMap<UpdateObjectADto, ObjectA>();
});
var mapper = config.CreateMapper();
新的..
var objectA = mapper.Map<ObjectA>(dto);
_dbContext.ObjectAs.Add(objectA);
...或更新現有。
var objectA = _dbContext.ObjectAs.Single(x => x.ObjectAId == dto.ObjectAId);
mapper.Map(objectA, dto);
DTO 反映創建新對象所需的數據,或允許客戶端更新以更新現有對象的數據。 目標是盡可能保持更新/添加/刪除操作的原子性,而不是傳遞大型復雜對象 /w 相關對象,以便一次全部更新。 即像“AddObjectBToA”“RemoveObjectBFromA”等操作,而不是通過單個“UpdateObjectA”解決所有操作。
遺憾的是,C# 不處理將 lambda 編譯為表達式,其中一個表達式調用另一個表達式。 特別是因為表達式樹可以表示這種情況。 但是 EF Core 3 或更高版本無論如何都不會查看調用表達式。
Automapper 可能更容易。 但是,如果您不想使用第 3 方代碼,則必須自己內聯表達式。 包括用方法的參數替換任何ParameterExpression
。
public static R Invoke<T, R>(this Expression<Func<T, R>> expression, T argument) => throw new NotImplementedException();
// etc for expressions with more parameters
public class InlineVisitor : ExpressionVisitor {
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Invoke"
&& node.Object == null
&& node.Arguments.Count >= 1
&& node.Arguments[0] is LambdaExpression expr)
return Visit(
new ReplacingExpressionVisitor(
expr.Parameters.ToArray(),
node.Arguments.Skip(1).ToArray())
.Visit(expr.Body)
);
return base.VisitMethodCall(node);
}
}
// usage;
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
var ToBorNotToB = ToObjectBDto();
Expression<Func<ObjectA, ObjectADto>> expr = a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = ToBorNotToB.Invoke(a.ObjectB)
};
return new InlineVisitor().VisitAndConvert(expr), "");
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.