Entity Framework linq orderby function - c#

I am new to Entity Framework and linq. I am working with asp.net mvc 5 and C#.
I write a query with dynamic sorting as follows:
public static IEnumerable<T> OrderByDynamic<T>(this IEnumerable<T> source, string propertyName, bool Ascending)
{
if (Ascending)
return source.OrderBy(x => x.GetType().GetProperty(propertyName).GetValue(x, null));
else
return source.OrderByDescending(x => x.GetType().GetProperty(propertyName).GetValue(x, null));
}
and in my repository I can write:
string sortExpr = "name";
_objDB.hotels.Where(s => (s.city_id = 1))
.OrderByDynamic(sortExpr, Ascending).ToList();
This code works fine when sorting is on a column of a table, but I need to sort by a SQL function. I entered the function into the .edmx model with the following code
[EdmFunction("hotelReservation.Core.Data", "getHotelMinPrice_cap")]
public static int getHotelMinPrice_cap(int Hotel_id, int capacity, DateTime enter_date, DateTime exit_date)
{
throw new NotSupportedException("Direct calls are not supported.");
}
and my SQL selection is something like:
select *
from hotel
where city_id = 1
order by dbo.getHotelMinPrice_cap(hotel.id,1,'2001-01-01', '2021-01-01')
How can I write the last SQL query with dynamic sorting in linq?

Your solution introduces several problems:
You OrderBy a property by name, hoping that the objects you order have this property. What if your objects don't have this property?
SQL does not understand functions like GetProperty(), so this ordering has to be done in local memory (AsEnumerable) instead of the much faster SQL server (AsQueryable).
You use a stored procedure to order by.
The first two problems can be solved easily by changing the parameter propertyName by a Func<TSource, TKey> keySelector:
public static IEnumerable<T, TKey> OrderByDynamic<T>(this IEnumerable<T> source,
Func<T, TKey> keySelector, System.ComponentModel.ListSortDirection sortDirection)
{
if (sortDirection == ListSortDirection.Ascending)
return source.OrderBy(keySelector);
else
return source.OrderByDescending(keySelector);
}
Usage would be like:
var result = Students.OrderByDynamis(student => student.Name, ListSortDirection.Ascending);
The advantage of this method is that your compiler will complain if you try to order by a non-existing property. Besides this OrderBy can be performed AsQueryable; it can be performed by your database instead of in local memory.
It is really a bad idea to use a string to select the property you want.
If you make a typing error you'll only detect this at run-time. Besides: if you know what to type as string for your propertyName during development of your code, you also know the type of objects you will be sorting, so you could write a keySelector instead.
Your second problem is calling the stored procedure as sort order. This is fairly easy to solve if you first call the stored procedure and then order by the returned value:
var result = Hotels.Where(hotel => hotel...)
.Select(hotel => new
{
StoredProcedureValue = CallStoredprocedure(hotel,...),
Hotel = hotel,
})
.AsEnumerable() // bring the result to local memory
.Orderby(item => item.StoredProcedureValue)
.Select(item => item.Hotel);
Only the hotels that will be in your end-result are transferred to local memory, together with the result of the StoredProcedures. They have only been called for the hotels you will use in your end result.
However, the sorting is done in local memory. If you also want this sorting to be done on database side you'll have to create a new stored procedure that will call the other stored procedure before performing the sort.

Thanks to Harald Coppoolse for the answer
I finally did it like this:
_objDB.hotels .Select(h=> new { id= h.id, name=h.name, star=h.star,
minPrice1 = hotel.getHotelMinPrice_cap(h.id, 1, model.date_check_in, model.date_check_out)})
.Where(s => (s.city_id = 1))
.OrderByDynamic(sortExpr, Ascending).ToList();
In this case, i can choose sortExpr = minPrice1; and it will be sort by the sql function.
also I changed the OrderByDynamic function as bellow:
public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
which i found on page:
https://stackoverflow.com/a/7265354/8509940

Related

Add a where clause to IQueryable without using generics

I'm trying to write a piece of code that is going to be used to select a value from a database. As a test I have already created this test version of the method that "works" but isn't great.
private object? FindObjectByProperty(IQueryable query, PropertyInfo primaryKeyProperty, object lookupValue)
{
object? result = null;
foreach (var value in query)
{
if (!primaryKeyProperty.GetValue(value)!.Equals(lookupValue))
continue;
result = value;
break;
}
return result;
}
The code will iterate through the query and find the first entry that matches the Equals. However, if the query contains millions of rows, as it's likely to when the IQueryable is really a DbSet (Which in this use case it is. Only I don't know what T, as it could be any of the DbSets on my EF Core data context.
What I'd like to end up with is something that looks something along these lines....
private object? FindObjectByProperty(IQueryable query, PropertyInfo primaryKeyProperty, object lookupValue)
{
return query.Where( x => x.*primaryKeyProperty* == lookupValue).FirstOrDefault();
}
The above code is pseudo code, the problems that I have is that query does not have a .Where available. Also primaryKeyProperty will need to be translated to the actual property.
The idea is that when this code is executed, ultimately executing the query, will generate a sql statement which selects a single item and returns it.
Can anyone help with solving this?
Update:
I'm working on a solution to this, so far this is what I've come up with
//using System.Linq.Dynamic.Core;
private object? FindObjectByProperty(IQueryable query, PropertyInfo primaryKeyProperty, object lookupValue)
{
var parameter = Expression.Parameter(query.ElementType);
var e1 = Expression.Equal(Expression.Property(parameter, primaryKeyProperty.Name), Expression.Constant(lookupValue));
var lambda = Expression.Lambda<Func<object, bool>>(e1, parameter);
return query.Where(lambda).FirstOrDefault();
}
This is failing on the because the func uses Object, when it really needs the real type. Trying to figure that bit out. This is getting closer.
Update #2:
Here's the answer that I needed
private object? FindObjectByProperty(IQueryable query, PropertyInfo primaryKeyProperty, object lookupValue)
{
var parameter = Expression.Parameter(query.ElementType);
var propExpr = Expression.Property(parameter, primaryKeyProperty);
var lambdaBody = Expression.Equal(propExpr, Expression.Constant(lookupValue, primaryKeyProperty.PropertyType));
var filterEFn = Expression.Lambda(lambdaBody, parameter);
return query.Where(filterEFn).FirstOrDefault();
}
The difference is that the Expression.Lambda no longer tries to define the func using generics. This is the only change that I needed to do to make the code function as I wanted.
In the test case that I was using, the T-SQL produced to lookup the value looks like this...
SELECT TOP(1) [g].[Id], [g].[Deleted], [g].[Guid], [g].[Name], [g].[ParentId]
FROM [Glossaries].[Glossaries] AS [g]
WHERE [g].[Id] = CAST(3 AS bigint)
The table [Glossaries].[Glossaries] is provided by the input query. The column name Id is provided by the primaryKeyProperty, and the number 3 is provided by the lookupValue.
This is perfect for my needs as I simply needed to select that one row and nothing else, so that I can effectively lazy load the my object property when I need it, and not before.
Also this code will be reused for many different tables.
In order to build add Where to an IQueryable where you don't have access to the actual IQueryable<T>, you need to take a step back (or up?) from calling Where at compile-time, and build the Where call at runtime, as well as the predicate lambda:
public static class DBExt {
public static IQueryable WherePropertyIs<T2>(this IQueryable src, PropertyInfo propInfo, T2 propValue) {
// return src.Where(s => s.{propInfo} == propValue)
// (T s)
var sParam = Expression.Parameter(src.ElementType, "s");
// s.propInfo
var propExpr = Expression.Property(sParam, propInfo);
// s.{propInfo} == propValue
var lambdaBody = Expression.Equal(propExpr, Expression.Constant(propValue));
// (T s) => s.{PropInfo} == propValue
var filterEFn = Expression.Lambda(lambdaBody, sParam);
var origQuery = src.Expression;
// IQueryable<Tx>.Where<Tx, Expression<Func<Tx, bool>>>()
var whereGenericMI = typeof(Queryable).GetMethods("Where", 2).Where(mi => mi.GetParameters()[1].ParameterType.GenericTypeArguments[0].GenericTypeArguments.Length == 2).First();
// IQueryable<T>.Where<T, Expression<Func<T, bool>>>()
var whereMI = whereGenericMI.MakeGenericMethod(src.ElementType);
// src.Where(s => s.{propertyInfo} == propValue)
var newQuery = Expression.Call(whereMI, origQuery, filterEFn);
return src.Provider.CreateQuery(newQuery);
}
}

Dynamically construct where clause with Func<T, string> lambda - linq to entities

(this is for .Net Framework 4.7)
I'm trying to write up some extension methods to aid in creating dynamic where clauses for various entities. I started a few days ago, so there's likely a lot that I don't know and some that I probably misunderstood.
I managed to create one extension method already for filtering by 1 property which works as I expect it to (I did use reflection to get the property, couldn't get it working with an interface - well, without it executing the sql that is).
I can't seem to be able to get this one working for a lambda expression though.
Note, that the solution must not trigger sql execution. Because I was able to write up some variants that "worK', but they will trigger sql execution.
The way I work with this is that once I have the code ready, I start debugging and have the "query" in the watch. And it looks like this (notice the sql code)
Once I step over my FilterString method call, it either turns into a sql result, or I get an exception (with current code), which it shouldn't:
So here's my current code that throws the exception (currently not dealing with the "match" parameter, I am implementing an "equals" call. There will be others like, starts With, like, etc)
The exception is just one of those "type mismatch" having function cannot be passed as param to string Equals or what not.
public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
string criteriaItem, Expression<Func<T, string>> getItemString)
where T : class
{
if (string.IsNullOrEmpty(criteriaItem))
{
return query;
}
var param = Expression.Parameter(typeof(T), "r");
var selector = Expression.Lambda<Func<T, string>>(getItemString, param);
Expression<Func<string, bool>> prototype = item => item == criteriaItem;
var predicate = Expression.Lambda<Func<T, bool>>(
prototype.Body.ReplaceParameter(prototype.Parameters[0], selector.Body),
selector.Parameters[0]);
return query.Where(predicate);
}
and the one that executes the sql instead of just generating it
public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
string criteriaItem, Expression<Func<T, string>> getItemString)
where T : class
{
if (string.IsNullOrEmpty(criteriaItem))
{
return query;
}
var param = Expression.Parameter(typeof(T), "r");
//var value = Expression.Constant(getItemString);
var equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
var item = Expression.Invoke(getItemString, param);
var body = Expression.Call(Expression.Constant(criteriaItem),
equals,
item);
return query.Where(Expression.Lambda<Func<T, bool>>(body, param));
}
calling these is done like so
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.MaybeSomeOtherProp.SomeString);
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.Name);
This same extension method will be called on any number of different entities, with nay number of different properties and prop names. I guess I could make use of the reflection version I got working and passing in all the property names in some array of some sort, but that is just plain ugly.
So long story short, how can I get this working in the way I explained above, taht is: having the sql generated instead of executed?
Thank you,
Note, the "ReplaceParameter" extension method is the one from here: https://stackoverflow.com/a/39206392/630515
So, you're trying to merge your prototype item => item == criteriaItem. With a passed in string property expression, like (r) => r.SomeProperty.Name to create (r) => r.SomeProperty.Name == criteriaItem.
Expression<Func<string, bool>> prototype = item => item == criteriaItem;
var predicate = Expression.Lambda<Func<T, bool>>(
ReplacingExpressionVisitor.Replace(
prototype.Parameters[0],
getItemString.Body,
prototype.Body),
getItemString.Parameters[0]);
And I think you're trying to do it this way so that criteriaItem is bound to an sql parameter, rather than being inlined as a string constant. But your question was a little hard to follow.

Combine Expressions instead of using multiple queries in Entity Framework

I have following generic queryable (which may already have selections applied):
IQueryable<TEntity> queryable = DBSet<TEntity>.AsQueryable();
Then there is the Provider class that looks like this:
public class Provider<TEntity>
{
public Expression<Func<TEntity, bool>> Condition { get; set; }
[...]
}
The Condition could be defined per instance in the following fashion:
Condition = entity => entity.Id == 3;
Now I want to select all Provider instances which have a Condition that is met at least by one entity of the DBSet:
List<Provider> providers = [...];
var matchingProviders = providers.Where(provider => queryable.Any(provider.Condition))
The problem with this: I'm starting a query for each Provider instance in the list. I'd rather use a single query to achieve the same result. This topic is especially important because of questionable performance. How can I achieve the same results with a single query and improve performance using Linq statements or Expression Trees?
Interesting challenge. The only way I see is to build dynamically UNION ALL query like this:
SELECT TOP 1 0 FROM Table WHERE Condition[0]
UNION ALL
SELECT TOP 1 1 FROM Table WHERE Condition[1]
...
UNION ALL
SELECT TOP 1 N-1 FROM Table WHERE Condition[N-1]
and then use the returned numbers as index to get the matching providers.
Something like this:
var parameter = Expression.Parameter(typeof(TEntity), "e");
var indexQuery = providers
.Select((provider, index) => queryable
.Where(provider.Condition)
.Take(1)
.Select(Expression.Lambda<Func<TEntity, int>>(Expression.Constant(index), parameter)))
.Aggregate(Queryable.Concat);
var indexes = indexQuery.ToList();
var matchingProviders = indexes.Select(index => providers[index]);
Note that I could have built the query without using Expression class by replacing the above Select with
.Select(_ => index)
but that would introduce unnecessary SQL query parameter for each index.
Here is another (crazy) idea that came in my mind. Please note that similar to my previous answer, it doesn't guarantee better performance (in fact it could be worse). It just presents a way to do what you are asking with a single SQL query.
Here we are going to create a query that returns a single string with length N consisting of '0' and '1' characters with '1' denoting a match (something like string bit array). The query will use my favorite group by constant technique to build dynamically something like this:
var matchInfo = queryable
.GroupBy(e => 1)
.Select(g =>
(g.Max(Condition[0] ? "1" : "0")) +
(g.Max(Condition[1] ? "1" : "0")) +
...
(g.Max(Condition[N-1] ? "1" : "0")))
.FirstOrDefault() ?? "";
And here is the code:
var group = Expression.Parameter(typeof(IGrouping<int, TEntity>), "g");
var concatArgs = providers.Select(provider => Expression.Call(
typeof(Enumerable), "Max", new[] { typeof(TEntity), typeof(string) },
group, Expression.Lambda(
Expression.Condition(
provider.Condition.Body, Expression.Constant("1"), Expression.Constant("0")),
provider.Condition.Parameters)));
var concatCall = Expression.Call(
typeof(string).GetMethod("Concat", new[] { typeof(string[]) }),
Expression.NewArrayInit(typeof(string), concatArgs));
var selector = Expression.Lambda<Func<IGrouping<int, TEntity>, string>>(concatCall, group);
var matchInfo = queryable
.GroupBy(e => 1)
.Select(selector)
.FirstOrDefault() ?? "";
var matchingProviders = matchInfo.Zip(providers,
(match, provider) => match == '1' ? provider : null)
.Where(provider => provider != null)
.ToList();
Enjoy:)
P.S. In my opinion, this query will run with constant speed (regarding number and type of the conditions, i.e. can be considered O(N) in the best, worst and average cases, where N is the number of the records in the table) because the database has to perform always a full table scan. Still it will be interesting to know what's the actual performance, but most likely doing something like this just doesn't worth the efforts.
Update: Regarding the bounty and the updated requirement:
Find a fast query that only reads a record of the table once and ends the query if already all conditions are met
There is no standard SQL construct (not even speaking about LINQ query translation) that satisfies both conditions. The constructs that allow early end like EXISTS can be used for a single condition, thus when executed for multiple conditions will violate the first rule of reading the table record only once. While the constructs that use aggregates like in this answer satisfy the first rule, but in order to produce the aggregate value they have to read all the records, thus cannot exit earlier.
Shortly, there is no query that can satisfy both requirements. What about the fast part, it really depends of the size of the data and the number and type of the conditions, table indexes etc., so again there is simply no "best" general solution for all cases.
Based on this Post by #Ivan I created an expression that is slightly faster in some cases.
It uses Any instead of Max to get the desired results.
var group = Expression.Parameter(typeof(IGrouping<int, TEntity>), "g");
var anyMethod = typeof(Enumerable)
.GetMethods()
.First(m => m.Name == "Any" && m.GetParameters()
.Count() == 2)
.MakeGenericMethod(typeof(TEntity));
var concatArgs = Providers.Select(provider =>
Expression.Call(anyMethod, group,
Expression.Lambda(provider.Condition.Body, provider.Condition.Parameters)));
var convertExpression = concatArgs.Select(concat =>
Expression.Condition(concat, Expression.Constant("1"), Expression.Constant("0")));
var concatCall = Expression.Call(
typeof(string).GetMethod("Concat", new[] { typeof(string[]) }),
Expression.NewArrayInit(typeof(string), convertExpression));
var selector = Expression.Lambda<Func<IGrouping<int, TEntity>, string>>(concatCall, group);
var matchInfo = queryable
.GroupBy(e => 1)
.Select(selector)
.First();
var MatchingProviders = matchInfo.Zip(Providers,
(match, provider) => match == '1' ? provider : null)
.Where(provider => provider != null)
.ToList();
The approach I tried here was to create Conditions and nest them into one Expression. If one of the Conditions is met, we get the index of the Provider for it.
private static Expression NestedExpression(
IEnumerable<Expression<Func<TEntity, bool>>> expressions,
int startIndex = 0)
{
var range = expressions.ToList();
range.RemoveRange(0, startIndex);
if (range.Count == 0)
return Expression.Constant(-1);
return Expression.Condition(
range[0].Body,
Expression.Constant(startIndex),
NestedExpression(expressions, ++startIndex));
}
Because the Expressions still may use different ParameterExpressions, we need an ExpressionVisitor to rewrite those:
private class PredicateRewriterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _parameterExpression;
public PredicateRewriterVisitor(ParameterExpression parameterExpression)
{
_parameterExpression = parameterExpression;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameterExpression;
}
}
For the rewrite we only need to call this method:
private static Expression<Func<T, bool>> Rewrite<T>(
Expression<Func<T, bool>> exp,
ParameterExpression parameterExpression)
{
var newExpression = new PredicateRewriterVisitor(parameterExpression).Visit(exp);
return (Expression<Func<T, bool>>)newExpression;
}
The query itself and the selection of the Provider instances works like this:
var parameterExpression = Expression.Parameter(typeof(TEntity), "src");
var conditions = Providers.Select(provider =>
Rewrite(provider.Condition, parameterExpression)
);
var nestedExpression = NestedExpression(conditions);
var lambda = Expression.Lambda<Func<TEntity, int>>(nestedExpression, parameterExpression);
var matchInfo = queryable.Select(lambda).Distinct();
var MatchingProviders = Providers.Where((provider, index) => matchInfo.Contains(index));
Note: Another option which isn't really fast as well
Here is another view of the problem that has nothing to do with expressions.
Since the main goal is to improve the performance, if the attempts to produce the result with single query don't help, we could try improving the speed by parallelizing the execution of the original multi query solution.
Since it's really a LINQ to Objects query (which internally executes multiple EF queries), theoretically it should be a simple matter of turning it into a PLINQ query by inserting AsParallel like this (non working):
var matchingProviders = providers
.AsParallel()
.Where(provider => queryable.Any(provider.Condition))
.ToList();
However, it turns out that EF DbContext is not well suited for multi thread access, and the above simply generates runtime errors. So I had to resort to TPL using one of the Parallel.ForEach overloads that allows us to supply local state, which I used to allocate several DbContext instances during the execution.
The final working code looks like this:
var matchingProviders = new List<Provider<TEntity>>();
Parallel.ForEach(providers,
() => new
{
context = new MyDbContext(),
matchingProviders = new List<Provider<TEntity>>()
},
(provider, state, data) =>
{
if (data.context.Set<TEntity>().Any(provider.Condition))
data.matchingProviders.Add(provider);
return data;
},
data =>
{
data.context.Dispose();
if (data.matchingProviders.Count > 0)
{
lock (matchingProviders)
matchingProviders.AddRange(data.matchingProviders);
}
}
);
If you have a multi core CPU (which is normal nowadays) and a good database server, this should give you the improvement you are seeking for.

Encapsulating LINQ queries in navigation properties for re-use?

I'm using Entity Framework Code First with SQL Server, with a domain entity that is similar to this:
public class Item
{
public ICollection<ItemLocation> ItemLocations { get; set; }
}
An item can be assigned to many locations throughout it's life, but only one can be active at any time, and we use this to get the actual location of the item:
public Location
{
get
{
return ItemLocations.Where(x => x.IsActive).Select(x => x.Location).FirstOrDefault()
}
}
This property works as expected if I load the entire item object:
var item = (from i in db.Items select i).FirstOrDefault();
Console.WriteLine(item.Location.Name);
However, I can't use this in my LINQ queries where I need to return an anonymous type, like this:
var items = from i in db.Items
select new
{
ItemId = i.ItemId,
LocationName = i.Location.Name
};
Instead, I have to use the full query every time:
var items = from i in db.Items
select new
{
ItemId = i.ItemId,
LocationName = i.ItemLocations.Where(x => x.IsActive).Select(x => x.Location).FirstOrDefault().Name
};
Ideally, I'd like to keep the logic for retrieving an item location in one place (like a property), rather than having to scatter these all over the place.
What is the best way to achieve this?
So to start with, if we want to be able to combine this sub-query with another query then we need to define it as an Expression object, rather than as C# code. If it has already been compiled into IL code then the query provider cannot inspect it to look at what operations are performed and translate that into SQL code. Creating an Expression representing this operation is easy enough:
public static readonly Expression<Func<Item, ItemLocation>> LocationSelector =
item => item.ItemLocations.Where(x => x.IsActive)
.Select(x => x.Location)
.FirstOrDefault();
Now that we have an expression to get a location from an item, we need to combine that with your custom expression for selecting out an anonymous object from an item, using this location. To do this we'll need a Combine method that can take one expression selecting an object into another object, as well as another expression that takes the original object, the result of the first expression, and computes a new result:
public static Expression<Func<TFirstParam, TResult>>
Combine<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], param)
.Replace(second.Parameters[1], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Internally, this simply replaces all instances of the parameter of the second expression with the body of the first; the rest of the code is simply ensuring a single parameter throughout and wrapping the result back into a new lambda. This code depends on the ability to replace all instances of one expression with another, which we can do using:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now that we have our Combine method all we need to do is call it:
db.Items.Select(Item.LocationSelector.Combine((item, location) => new
{
ItemId = item.ItemId,
LocationName = location.Name
}));
And voila.
If we wanted, we could print out the expression generated by the call to Combine instead of passing it to Select. Doing that, it prints out:
param => new <>f__AnonymousType3`2(ItemId = param.ItemId,
LocationName = param.ItemLocations.Where(x => x.IsActive)
.Select(x => x.Location).FirstOrDefault().Name)
(whitespace added by myself)
That is exactly the query that you had specified out manually, however here we're re-using the existing sub-query without needing to type it out every single time.

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.
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;

Categories