有没有办法在 lambda 表达式树中使用“动态”?

Posted

技术标签:

【中文标题】有没有办法在 lambda 表达式树中使用“动态”?【英文标题】:Is there a way to use `dynamic` in lambda expression tree? 【发布时间】: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 基础上工作吗?

【问题讨论】:

【参考方案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。

以上是关于有没有办法在 lambda 表达式树中使用“动态”?的主要内容,如果未能解决你的问题,请参考以下文章

哪位高人指点一下,C#中两个动态lambda 表达式有啥办法合并成一个? 谢谢!

有没有办法规避 lambda 表达式的类型化?

有没有办法比较 lambdas?

有没有办法在 lambda 表达式中显式定义泛型参数类型?

如何在 Enumerable.Distinct() 中使用 lambda 表达式 [重复]

基于Expression Lambda表达式树的通用复杂动态查询构建器——《剧透一下》