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.
Expression<Func<T,bool>>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 anIdproperty, or you could use it to find the index of an item based on something other than its Id..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