实体框架核心在导航属性上调用表达式

时间:2020-10-19 21:35:15

标签: c# entity-framework linq .net-core

试图对此进行大量搜索,但无法给出有效的答案。这是我想要做的:

我有一个实体ObjectA,它的导航属性为ObjectB(不是Collection类型,它是一个虚拟属性,可以通过延迟加载来加载)。当我对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)
  };
}

2 个答案:

答案 0 :(得分:2)

我的建议是为自己节省工作并利用AutoMapper。这样做的好处是Automapper可以通过master馈送EF的IQueryable实现,以构建查询并填充DTO图。

ProjectTo

可以在映射中设置无法在对象和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); 与自定义映射相比的优势在于,它将构建相关的查询,而不会导致延迟加载命中,也不会冒EF无法转换为SQL的触发代码的风险。 (一个查询可填充所有相关的DTO)

Automapper可以协助将DTO中的值复制回新实体或更新现有实体:

ProjectTo

新..

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

DTO在其中反映了创建新对象所需的数据,或允许客户端更新以更新现有对象的数据。目标是保持更新/添加/删除操作尽可能原子化,而不是传递大型复杂对象/ w亲戚一次全部更新。即之类的操作,例如“ AddObjectBToA”,“ RemoveObjectBFromA”等,而不是通过单个“ UpdateObjectA”解决所有操作。

答案 1 :(得分:1)

很遗憾,C#无法处理将lambda编译为表达式,其中一个表达式调用另一个表达式。特别是因为表达式树可以表示这种情况。但是,EF Core 3或更高版本无论如何都不会浏览调用表达式。

Automapper可能更容易。但是,如果您不想使用第三方代码,则必须自己内联表达式。包括用方法的参数替换任何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), "");
}