簡體   English   中英

Entity Framework Core 在導航屬性上調用表達式

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM