有没有办法在lambda表达式树中使用`dynamic`?

一,规格。 我们使用MVC5,.NET 4.5.1和Entity框架6.1。

在我们的MVC5业务应用程序中,我们有很多重复的CRUD代码。 我的工作是“自动化”大部分内容,这意味着将其提取到基类并使其可重用。 现在,我有控制器,视图模型和EF6实体模型的基类。

我的抽象基类,所有EF6实体都inheritance:

public abstract class BaseEntity where TSubclass : BaseEntity { public abstract Expression<Func> UpdateCriterion(); } 

UpdateCriterion方法用于数据库上下文的AddOrUpdate方法。 我有一个子类的generics参数,因为UpdateCriterion需要返回使用精确子类类型的lambda表达式,而不是接口或基类。 实现此抽象基类的极其简化的子类如下所示:

 public class Worker : BaseEntity { public int ID { get; set; } public int Name { get; set; } public override Expression<Func> UpdateCriterion() { return worker => worker.ID; } } 

在那之后,在我的基本控制器的SaveOrUpdate操作中,我会有这样的代码:

 public ActionResult Save(TViewModel viewModel) { if (ModelState.IsValid) { var entityModel = viewModel.ConstructEntityModel(); db.Set().AddOrUpdate(entityModel.UpdateCriterion(), entityModel); db.SaveChanges(); } } 

多亏了这一点,基本控制器的子类不需要像以前那样自己实现Save方法。 现在,所有这些都有效,并且它实际上运行得很好,尽管语法很时髦(我的意思是, class BaseEntity where TSubclass : BaseEntity ,认真吗?)。

这是我的问题。 对于大多数实体而言,字段ID是关键,但对于某些实体而言,它不是,所以我无法通过超类实现正确地概括。 所以现在,每个实体子类都实现了它自己的UpdateCriterion 。 但是,因为对于大多数(90%以上)实体e => e.ID是正确的实现,我有很多重复。 所以我想将实体基类重写为:

 public abstract class BaseEntity where TSubclass : BaseEntity { public virtual Expression<Func> UpdateCriterion() { return entity => ((dynamic)entity).ID; } } 

目的是提供使用ID作为键的默认实现,并允许子类在使用不同键时覆盖它。 我不能使用具有ID字段的接口或基类,因为并非所有实体都具有它。 我以为我会使用dynamic来提取ID字段,但是我得到以下错误: Error: An expression tree may not contain a dynamic operation

那么,关于如何做到这一点的任何想法? reflection是否可以在UpdateCriterion基础上工作?

不,您不能在Linq to Entities查询中使用动态。 但是您可以在运行时构建Lambda Expression。

 public virtual Expression> UpdateCriterion() { var param = Expression.Parameter(typeof(TSubclass)); var body = Expression.Convert(Expression.Property(param, "ID"), typeof(object)); return Expression.Lambda>(body, param); } 

如果TSubclass类型没有ID属性Expression.Property(param,“ID”)将抛出exception。

此外,您可以使用实体模型中的MetadataWorkspace来获取TSubclass的主键列。

如果要定义BaseEntity类,则可以添加一个返回实际ID属性的虚拟只读属性。 我相信EF将只读属性视为“计算”,因此它们不会存储到数据库中。

 public abstract class BaseEntity where TSubclass : BaseEntity { public abstract object ID { get; } public virtual Expression> UpdateCriterion() { return entity => entity.ID; } } public partial class Foo : BaseEntity { public Int32 FooId { get; set; } public override object ID { get { return FooId; } } } 

只是一个想法 – 我只尝试在LinqPad中编译并检查对UpdateCriterion的调用值。 🙂

看看这个答案 。 它使用reflection来获取ID属性。 我认为它解决了你的问题:

 public static object GetPropValue(object src, string propName) { return src.GetType().GetProperty(propName).GetValue(src, null); } 

然后用你的lambda表达式替换

 return entity => GetPropValue(entity, "ID"); 

我没有测试过,因为我没有完全可以测试它的代码。 如果有效,请告诉我们。