首先,规范。我们使用MVC5,.NET 4.5.1和实体框架6.1。
在我们的MVC5业务应用程序中,我们有很多重复的CRUD代码。我的工作是"自动化"大部分,这意味着将其提取到基类并使其可重用。现在,我有控制器,视图模型和EF6实体模型的基类。
我的所有EF6实体继承的抽象基类:
public abstract class BaseEntity<TSubclass>
where TSubclass : BaseEntity<TSubclass>
{
public abstract Expression<Func<TSubclass, object>> UpdateCriterion();
}
UpdateCriterion
方法用于数据库上下文的AddOrUpdate
方法。我有一个子类的泛型参数,因为UpdateCriterion
需要返回使用精确子类类型的lambda表达式,而不是接口或基类。实现此抽象基类的极其简化的子类如下所示:
public class Worker : BaseEntity<Worker>
{
public int ID { get; set; }
public int Name { get; set; }
public override Expression<Func<Worker, object>> UpdateCriterion()
{
return worker => worker.ID;
}
}
之后,在我的基本控制器的SaveOrUpdate
动作中,我会得到这样的代码:
public ActionResult Save(TViewModel viewModel)
{
if (ModelState.IsValid)
{
var entityModel = viewModel.ConstructEntityModel();
db.Set<TEntityModel>().AddOrUpdate<TEntityModel>(entityModel.UpdateCriterion(), entityModel);
db.SaveChanges();
}
}
由于这一点,基本控制器的子类不需要像以前那样自己实现Save
方法。现在,所有这些都有效,尽管语法很时髦(我的意思是,class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>
,但是它真的很有用吗?)。
这是我的问题。对于大多数实体,字段ID
是关键,但对于某些实体而言,它不是,所以我无法通过超类实现进行适当的推广。所以现在,每个实体子类都实现它自己的UpdateCriterion
。但是,因为对于大多数(90%以上)实体e => e.ID
是正确的实现,我有很多重复。所以我想将实体基类重写为:
public abstract class BaseEntity<TSubclass>
where TSubclass : BaseEntity<TSubclass>
{
public virtual Expression<Func<TSubclass, object>> UpdateCriterion()
{
return entity => ((dynamic)entity).ID;
}
}
目的是提供使用ID作为键的默认实现,并允许子类在使用不同键时覆盖它。我无法使用ID字段的接口或基类,因为并非所有实体都拥有它。我以为我会使用dynamic
提取ID
字段,但我收到以下错误:Error: An expression tree may not contain a dynamic operation
。
那么,关于如何做到这一点的任何想法?反思是否会在UpdateCriterion
基础上发挥作用?
答案 0 :(得分:1)
如果要定义BaseEntity类,则可以添加一个返回实际ID属性的虚拟只读属性。我相信EF将只读属性视为“计算”,因此它们不会存储到数据库中。
public abstract class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>
{
public abstract object ID { get; }
public virtual Expression<Func<TSubclass, object>> UpdateCriterion()
{
return entity => entity.ID;
}
}
public partial class Foo : BaseEntity<Foo>
{
public Int32 FooId { get; set; }
public override object ID { get { return FooId; } }
}
只是一个想法 - 我只尝试在LinqPad中编译并检查对UpdateCriterion的调用值。 :)
答案 1 :(得分:1)
不,您不能在Linq to Entities查询中使用动态。 但是您可以在运行时构建Lambda表达式。
public virtual Expression<Func<TSubclass, object>> UpdateCriterion()
{
var param = Expression.Parameter(typeof(TSubclass));
var body = Expression.Convert(Expression.Property(param, "ID"), typeof(object));
return Expression.Lambda<Func<TSubclass, object>>(body, param);
}
如果TSubclass类型没有ID属性Expression.Property(param,&#34; ID&#34;)将抛出异常。
此外,您可以使用实体模型中的MetadataWorkspace来获取TSubclass的主键列。
答案 2 :(得分:0)
看看this answer。它使用反射来获取ID属性。我认为它解决了你的问题:
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
然后用
替换你的lambda表达式return entity => GetPropValue(entity, "ID");
我没有经过测试,因为我没有完全用于测试它的代码。如果有效,请告诉我们。