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

时间:2014-07-04 13:30:32

标签: c# .net linq entity-framework lambda

首先,规范。我们使用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基础上发挥作用?

3 个答案:

答案 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");

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