Shawn Crane
Shawn Crane

Reputation: 51

Dynamic Lambda Select Index

I need some help with a LINQ extension that I'm tying to write. I'm trying to create an extension that calculates the row index of a given Id within an IQueryable - Except that type can be any table. I think I've got most of the way there but I just can't seem to complete it. I'm getting the following error message on the line

Select(lambda)

The type arguments for method 'System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. c:\users\shawn_000\documents\visual studio 2013\projects\dexconstruktaweb\dexconstruktaweb\generalhelper.cs 157 17 DexConstruktaWeb

private class GetRowCountClass
{
    public GetRowCountClass(int id, int index)
    {
        this.Id = id;
        this.Index = index;
    }

    public int Id { get; set; }
    public int Index { get; set; }
}

public static int GetRowCount<T>(this IQueryable<T> query, int id)
{
    Type sourceType = typeof(T);            
    ParameterExpression[] parameter = new ParameterExpression[2];
    parameter[0]  = Expression.Parameter(sourceType, "x");
    parameter[1] = Expression.Parameter(typeof(int), "index");
    Type getRowCountType = typeof(GetRowCountClass);
    ConstructorInfo constructor = getRowCountType.GetConstructor(new[] { typeof(int), typeof(int)} );

    PropertyInfo pi = sourceType.GetProperty("Id");
    Expression expr = Expression.Property(parameter[0], pi);
    NewExpression member = LambdaExpression.New(constructor,new Expression[] { expr, parameter[1]});

    LambdaExpression lambda = Expression.Lambda(member, parameter);

    var item = query.AsEnumerable()
        .Select(lambda);
}

I know that after the select I need the following line to get the index to return, but for now I'm stumped. Any help would be appreciated. Thanks.

.SingleOrDefault(x => x.Id == id).index;

Update

I've done some further digging and found that some LINQ statements do not work for LINQ to Entities, which is what I'm using:

http://msdn.microsoft.com/en-us/library/bb738550.aspx

http://msdn.microsoft.com/en-us/library/bb896317.aspx

In particular "Most overloads of the projection and filtering methods are supported in LINQ to Entities, with the exception of those that accept a positional argument."

To get around this I was using a call to AsEnumerable() to turn this into a generic Enumerable, then the call to Select and SingleOrDefault as described above. However, I have found that there is no difference in the SQL created between a call to AsEnumerable and ToList, so I have decided to simply call:

.ToList().FindIndex(e => e.Id == id) 

directly on my IQueryable without creating an Extension as it is a small enough piece of code.

Thanks for all your help. If someone still sees a better way to do this please let me know.

cheers,

Update 2

As a bit of a learning exercise I took Servy's suggestion and this answer Creating Dynamic Predicates- passing in property to a function as parameter and came up with the following:

public static int GetRowIndex<T>(this IQueryable<T> query, Expression<Func<T, int>> property, int id)
{
    var lambda = Expression.Lambda<Predicate<T>>(
            Expression.Equal(property.Body, Expression.Constant(id)), property.Parameters);

    return query.ToList().FindIndex(lambda.Compile());
}

This can be called like:

var result2 = query.GetRowIndex(x => x.Id, id);

Where query is of Type IQueryable.

There is very little point to it though and it is only really useful as a learning exercise.

Thanks.

Upvotes: 1

Views: 2059

Answers (1)

MarcinJuraszek
MarcinJuraszek

Reputation: 125630

Your lambda always returns GetRowCountClass and takes T so you can use generic version of Expression.Lambda method:

var lambda = Expression.Lambda<Func<T, GetRowCountClass>>(member, parameter);

var item = query.Select(lambda);

return item.SingleOrDefault(x => x.Id == id).Index;

Upvotes: 1

Related Questions