linq 实体框架核心在导航属性上创建表达式

mnemlml8  于 2023-09-28  发布在  其他
关注(0)|答案(3)|浏览(112)

试着搜索了很多,但无法找到一个有效的答案。这就是我正在尝试做的:
我有一个实体ObjectA,它有一个导航属性ObjectB(不是一个集合类型,它是一个通过延迟加载加载的虚拟属性)。当我对A执行Where查询时,我还想使用MapB的另一个表达式来扩展表达式中的属性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)
  };
}
6rqinv9w

6rqinv9w1#

我的建议是保存您的工作并利用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之间推断出的特定Map都可以在Map中设置。ProjectTo与自定义Map的一个优点是,它将构建相关查询,而不会触发延迟加载命中或冒触发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”来解析所有操作。

oymdgrw7

oymdgrw72#

遗憾的是,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), "");
}
pgccezyw

pgccezyw3#

使用LinqKit.Expand()扩展方法。Expand搜索它被调用的表达式树,并用expression的实际内容替换内部的expression.Invoke()方法。它允许表达式很容易地组成。
举例来说:

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 = ToObjectBDto().Invoke(a.ToObjectB.Invoke()) // Use Invoke() here
  })
}

// Expand() replaces ToObjectBDto().Invoke() with the actual expression tree returned by ToObjectBDto()
var aDto = _dbContext.ObjectAs.Where(q => q.SomeProperty > 0).Select(ToObjectADto().Expand());

相关问题