Need to build expression tree for max date value

.net c# entity-framework expression-trees linq

Question

I'm trying to build an expression tree for this linq query: so I can pass in a generic Entity:

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

I am wanting to create a class that takes a generic Entity and finds the max of its TimeStamp property.

I was trying something like below, but it complains:

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();

When I compile, I get the following error on the last line of code:

Overload resolution failed because no accessible 'Select' can be called with these arguments:

What am I doing wrong?

Accepted Answer

(As per comments...)

The problem is that you're trying to use a mixture of LINQ to Objects (which uses IEnumerable<T> and delegates) and Queryable-based LINQ (which uses IQueryable<T> and expression trees). You can't pass Enumerable<T> an expression tree.

Three options:

  • Convert the collection to an IQueryable<T> first:

    DateTime maxdate = this.EntityCollection.AsQueryable().Select(lambda).Max();
    
  • Convert the expression tree to a delegate first:

    DateTime maxdate = this.EntityCollection.Select(lambda.Compile()).Max();
    
  • Change your method to accept an IQueryable<T> instead of an IEnumerable<T>


Popular Answer

Personally, I prefer the overload of Expression.Property which takes a PropertyInfo instance.

Doing that, you could do this:

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();

It's just much cleaner.

It's possible that the call to Type.GetProperty is not returning anything, and that is giving you the error. Remember, the property name passed as a parameter has to be public, otherwise, you need to use the overload of GetProperty which allows you to specify values from the BindingFlags enumeration to indicate that you want non-public properties to be included.

However, I think there is a better alternative. You should define an interface like so:

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

That allows you to then define your extension method like so:

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

Note: DateTime? is used instead of DateTime in the event you have an empty sequence. Also, you can create an overload that takes an IQueryable<T> if you want execution to occur on a server.

The main benefit that you gain here is you gain compile-time checking of where the calls are valid. This is much better than having an exception thrown at runtime.

Also, it wouldn't be difficult to implement; you are using Entity Framework which creates partial class files; because of this, it's easy to add another partial class file for each type that has this:

public partial class MyEntity : IHaveTimeStamp
{ }

Your original code indicates that you have the TimeStamp property already on each of the entities that you want to use this extension method on, because of that, you don't need to do anything to implement the interface, it's already implicitly implemented for you because the TimeStamp property should be public.

If it's not public, then you can change your definition to easily be this:

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

Either way, it's a simple copy-and-paste job with some tweaking of the class name each time.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow