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



尝试了大量搜索,但无法找到有效的答案。以下是我要做的:

我有一个实体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)
};
}

我的建议是节省工作并利用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或更高版本无论如何都不会查看invoke表达式。

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

使用LinqKit的.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());

最新更新