有没有办法在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");
我没有测试过,因为我没有完全可以测试它的代码。 如果有效,请告诉我们。