【问题标题】:Need to build expression tree for max date value需要为最大日期值构建表达式树
【发布时间】:2011-08-10 17:46:47
【问题描述】:

我正在尝试为这个 linq 查询构建一个表达式树:所以我可以传入一个通用实体:

this.EntityCollection.Select((ent) => ent.TimeStamp).Max()

我想创建一个类,它采用通用实体并找到其 TimeStamp 属性的最大值。

我正在尝试类似下面的东西,但它抱怨:

ParameterExpression param = Expression.Parameter(typeof(TE), "ent");

MemberExpression prop = Expression.
    Property(param, typeof(TE).GetProperty("TimeStamp").GetGetMethod());

Expression<Func<TE, DateTime>> lambda = Expression.Lambda<Func<TE, DateTime>>(
    prop, new ParameterExpression[] { param });

DateTime maxdate = this.EntityCollection.Select(lambda).Max();

当我编译时,我在最后一行代码中得到以下错误:

重载解析失败,因为无法访问“选择” 使用这些参数调用:

我做错了什么?

【问题讨论】:

  • “它抱怨”含糊不清。请告诉我们您收到的确切错误。 (另外,您可以给 Expression.Property 一个属性 name - 这比自己获取它更简单。)
  • 在最后一行代码中出现错误“重载解析失败,因为无法使用这些参数调用可访问的 'Select':”
  • EntityCollection 是什么类型,TE 是什么?
  • 接受表达式树需要IQueryable&lt;T&gt;。
  • 如果你有有一个内存集合,你可以使用lambda.Compile()。将其保留为IQueryable&lt;T&gt; 的好处是在数据库中执行查询。

标签: c# .net linq entity-framework expression-trees


【解决方案1】:

(根据 cmets...)

问题是您尝试混合使用 LINQ to Objects(使用 IEnumerable&lt;T&gt; 和委托)和基于可查询的 LINQ(使用 IQueryable&lt;T&gt; 和表达式树)。您不能将 Enumerable&lt;T&gt; 传递给表达式树。

三个选项:

  • 首先将集合转换为IQueryable&lt;T&gt;:

    DateTime maxdate = this.EntityCollection.AsQueryable().Select(lambda).Max();
    
  • 先将表达式树转换为委托:

    DateTime maxdate = this.EntityCollection.Select(lambda.Compile()).Max();
    
  • 更改您的方法以接受 IQueryable&lt;T&gt; 而不是 IEnumerable&lt;T&gt;

【讨论】:

    【解决方案2】:

    就个人而言,我更喜欢overload of Expression.Property which takes a PropertyInfo instance。

    这样做,你可以这样做:

    ParameterExpression param = Expression.Parameter(typeof(TE), "ent");
    MemberExpression prop = Expression.
        Property(param, typeof(TE).GetProperty("TimeStamp"));
    Expression<Func<TE, DateTime>> lambda = Expression.Lambda<Func<TE, DateTime>>(
        prop, new ParameterExpression[] { param });
    DateTime maxdate = this.EntityCollection.Select(lambda).Max();
    

    它更干净。

    对Type.GetProperty 的调用可能没有返回任何内容,这会给您带来错误。请记住,作为参数传递的属性名称必须是公共的,否则,您需要使用overload of GetProperty which allows you to specify values from the BindingFlags enumeration 表示您希望包含非公共属性。

    但是,我认为还有更好的选择。你应该像这样定义一个接口:

    public interface IHaveTimestamp
    {
        DateTime TimeStamp { get; set; }
    }
    

    这使您可以像这样定义扩展方法:

    public static DateTime? MaxTimeStamp(IEnumerable<T> entities) 
        where T : IHaveTimeStamp
    {
        // Return the max.
        return entities.Select(e => (DateTime?) e.TimeStamp).Max();
    }
    

    注意:如果您有一个空序列,则使用 DateTime? 代替 DateTime。此外,如果您希望在服务器上执行,您可以创建一个采用 IQueryable&lt;T&gt; 的重载。

    您在这里获得的主要好处是您可以在编译时检查调用的有效位置。这比在运行时抛出异常好多。

    而且,实施起来并不难;您正在使用创建partial class files 的实体框架;因此,很容易为每种类型添加另一个分部类文件:

    public partial class MyEntity : IHaveTimeStamp
    { }
    

    您的原始代码表明您已经在要使用此扩展方法的每个实体上拥有 TimeStamp 属性,因此,您无需执行任何操作来实现接口,它已经为您隐式实现,因为TimeStamp 属性应该是公开的。

    如果它不公开,那么您可以轻松地将定义更改为:

    public partial class MyEntity : IHaveTimeStamp
    { 
        IHaveTimeStamp.TimeStamp
        { 
            get { return this.TimeStamp; } 
            set { this.TimeStamp = value; } 
        }
    }
    

    无论哪种方式,这都是一个简单的复制和粘贴工作,每次都会对类名进行一些调整。

    【讨论】:

    • 感谢您的好建议。我会像你说的那样实现其中的一些。
    【解决方案3】:
    ParameterExpression param = Expression.Parameter(typeof(TE), "ent");
    MemberExpression prop = Expression.Property(param, 
        typeof(TE).GetProperty("TimeStamp"));
    Expression<Func<TE, DateTime>> lambda = Expression.Lambda<Func<TE, DateTime>>(
        prop, new ParameterExpression[] { param });
    DateTime maxdate = this.EntityCollection.Select(lambda).Max();
    

    您无需在Property 上致电GetGetMethod。

    【讨论】:

    • 删除 'GetGetMethod' 后我仍然遇到同样的错误
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多