有没有办法在 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<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
基础上工作吗?
【问题讨论】:
【参考方案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 => new e.ID, e.Name
?
你知道如何使用 Expression 类构建e => new e.ID, e.Name?
吗?
在这种情况下,主体将是 Expression.MemberInit。问题是匿名类型。如果您使用 p => new p.Prop1, p.Prop2 编写 Lambda,编译器会创建一个具有匹配属性的具体类型。如果您在运行时创建 MemberInit,则匿名类型不存在,因此您必须提供有效的现有类型。 Expression.MemberInit(Expression.New(typeof(PrimaryKeyWithTwoValues<int,string>)),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 表达式有啥办法合并成一个? 谢谢!