1

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.

Community
  • 1
  • 1
Shawn Crane
  • 43
  • 1
  • 7
  • Why not just accept the predicate as a parameter? If you accept an `Expression>` it not only makes this method's implementation pretty trivial, but makes everything statically typed; you don't need to worry about calling it on something that doesn't have an `Id` property, or you could use it to find the index of an item based on something other than its Id. – Servy Jan 16 '14 at 19:11
  • @Servy This sounds like a better way to proceed then what I have done so far. Would you mind fleshing it out a little in an answer? – Shawn Crane Jan 17 '14 at 09:03
  • if you add `.ToList`, or `.AsEnumerable`, then all data from that query will be loaded to server side, and your FindIndex will work on loaded data. It won't be translated into SQL – Sergey Litvinov Jan 19 '14 at 18:52
  • @SergeyLitvinov True. But I am not aware of any way to get the Index of an item without returning all the data, while still using LINQ to Entities. – Shawn Crane Jan 19 '14 at 19:10

1 Answers1

1

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;
MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
  • Thanks for the response. Unfortunately this doesn't work. As I'm using LINQ to Entities I need to include .AsEnumerable() before the Select. However, this gives the error 'System.Collections.Generic.IEnumerable' does not contain a definition for 'Select' and the best extension method overload 'System.Linq.Queryable.Select(System.Linq.IQueryable, System.Linq.Expressions.Expression>)' has some invalid argument – – Shawn Crane Jan 17 '14 at 09:39
  • Just to clarify, if I don't include AsEnumerable then I get the following error when executing .SingleOrDefault: LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[Entities.GeneralHelpers.LinqExtensions+GetRowCountClass] Select[Book,GetRowCountClass](System.Linq.IQueryable`1[Entities.BLL.Models.Book], System.Linq.Expressions.Expression`1[System.Func`3[Entities.BLL.Models.Book,System.Int32,Entities.GeneralHelpers.LinqExtensions+GetRowCountClass]])' method, and this method cannot be translated into a store expression. – Shawn Crane Jan 17 '14 at 09:46