【问题标题】:Is there a way to use `dynamic` in lambda expression tree?有没有办法在 lambda 表达式树中使用“动态”?
【发布时间】:2014-08-25 20:44:18
【问题描述】:

首先,规格。我们使用 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&lt;TSubclass&gt; where TSubclass : BaseEntity&lt;TSubclass&gt;,认真的?),它实际上工作得很好。

我的问题来了。对于大多数实体字段ID 是关键,但对于某些不是,所以我不能用超类实现正确概括。所以现在,每个实体子类都实现了它自己的UpdateCriterion。但是,因为对于大多数(90%+)实体e =&gt; 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 基础上工作吗?

【问题讨论】:

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


    【解决方案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, "ID") 将抛出异常。

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

    【讨论】:

    • 谢谢,这正是我需要的!
    • 哦,另外,您知道如何在正文部分添加多个字段吗?喜欢e =&gt; new {e.ID, e.Name}?
    • 你知道如何使用 Expression 类构建e =&gt; new {e.ID, e.Name}? 吗?
    • 在这种情况下,主体将是 Expression.MemberInit。问题是匿名类型。如果您使用 p => new { p.Prop1, p.Prop2 } 编写 Lambda,编译器会创建一个具有匹配属性的具体类型。如果您在运行时创建 MemberInit,则匿名类型不存在,因此您必须提供有效的现有类型。 Expression.MemberInit(Expression.New(typeof(PrimaryKeyWithTwoValues&lt;int,string&gt;)),Expression.Bind(...)); 或使用 Reflection.Emit 动态创建一个。
    • 谢谢你,你真棒。你有什么资源可以让我学习这样的东西吗?我发现的关于 lambdas 的一切都只是匿名函数和一些 LINQ。
    【解决方案2】:

    如果您要定义 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 的调用的值。 :)

    【讨论】:

      【解决方案3】:

      看看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");
      

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

      【讨论】:

      • 我很确定他不需要值,他需要表达式。
      • 没问题。 Lambda Exp 可以有方法调用(某些特定情况除外,例如编译为 SQL 的表达式)
      • 是的,但在这种情况下,结果用于 DbSet 的 AddOrUpdate 方法。而且这个方法需要PropertyExpression。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-31
      • 2023-03-26
      • 2016-07-20
      • 2010-12-17
      相关资源
      最近更新 更多