Try to implement custom linq query to order dataset by any field (first take rows, which field is equal to the search string, then take all others).
Final query should be like this:
source.OrderBy(p => p.Phone == "1234567")
Code:
var type = typeof(T);
var property = type.GetProperty(sortPropertyOrder.FieldName.ToString());
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
string methodName = sortPropertyOrder.SortDirection == ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
Type[] typeArguments = new Type[] { type, property.PropertyType };
object fieldValue = Convert(property.PropertyType, sortPropertyOrder.FieldValue);
Expression equalExp = Expression.Equal(propertyAccess, Expression.Constant(fieldValue));
Expression finalExpression = Expression.Quote(Expression.Lambda(equalExp, parameter));
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, finalExpression);
IQueryable<T> query = source.Provider.CreateQuery<T>(resultExp);
As a result, I have an exception: No generic method 'OrderBy' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
Although I created more simple query(source.OrderBy(p => p.Phone))
var type = typeof(T);
var property = type.GetProperty(sortPropertyOrder.FieldName.ToString());
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
string methodName = sortPropertyOrder.SortDirection == ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
Type[] typeArguments = new Type[] { type, property.PropertyType };
object fieldValue = Convert(property.PropertyType, sortPropertyOrder.FieldValue);
Expression finalExpression = Expression.Quote(Expression.Lambda(propertyAccess, parameter));
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, finalExpression);
IQueryable<T> query = source.Provider.CreateQuery<T>(resultExp);
It works fine.
What's wrong with first query?
Related
I have the following code:
case FilterQueryType.Contains:
var parameterExp = Expression.Parameter(type, "type");
var propertyExp = Expression.Property(parameterExp, filter.PropertyName);
var containsConstExp = Expression.Constant(filter.MyKeyword);
MethodInfo method = typeof(string).GetMethod("Contains", new []{typeof(string)});
var containsMethodExp = Expression.Call(propertyExp, method, containsConstExp);
var containsLambda = Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
items = items.Where(containsLambda);
break;
This code works fine as long as filter.PropertyName is a string.
Now I have a case where filter.PropertyName is actually an enumerable of strings.
Could someone tell me how can I create the correct expression for this? (filter.MyKeyword itself will always be a single value)
MemberExpression memberExpression = Expression.Property(parameterExp, filter.PropertyName);
Expression convertExpression = Expression.Convert(memberExpression, typeof(List<string>));
MethodCallExpression containsExpression = Expression.Call(convertExpression, "Contains", new Type[] { }, constExpr);
lambda = Expression.Lambda<Func<T, bool>>(containsExpression, parameterExp);
items = items.Where(lambda);
This solution works for me
I want to create an extension method for a LINQ expression but I'm stuck. What I need is just to create a method which will add a specific Where clause to a Queryable. Something like:
var hierarchy = "a string";
Session.Query<SomeClass>.Where(x => x.Layer.Hierarchy.StartsWith(hierarchy) ||
x.Layer.Hierarchy == hierarchy);
to become:
var hierarchy = "a string";
Session.Query<SomeClass>.LayerHierarchy(x => x.Layer, hierarchy);
And do that Where logic inside. So basicly the extension method LayerHierarchy() is running over the Queryable of T but the subject is of type Layer:
public static IQueryable<T> LayerHierarchy<T>(this IQueryable<T> query,
Expression<Func<T, Layer>> layer,
string hierarchy)
{
var parameterExp = Expression.Parameter(typeof(Layer), "layer");
var propertyExp = Expression.Property(parameterExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
var startsWith = Expression.Lambda<Func<Layer, bool>>(methodExpStartsWith, parameterExp);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var equals = Expression.Lambda<Func<Layer, bool>>(methodExpEquals, parameterExp);
return query
.Where(startsWith)
.Where(equals);
}
Everything works fine above the return line. It complains that...
Cannot convert from System.Linq.Expressions.Expression<System.Func<Layer, bool>> to System.Linq.Expressions.Expression<System.Func<T, int, bool>>
when trying to pass the expressions to query.Where() method. How can I fix it?
Well, the problem is how you are creating the Lambdas. They should begin from T, not from Layer:
var startsWith = Expression.Lambda<Func<T, bool>>(methodExpStartsWith, parameterExp);
var equals = Expression.Lambda<Func<T, bool>>(methodExpEquals, parameterExp);
However, in order for this to work, you are missing one more PropertyExpression.
Your query now looks like:
(Layer)x => x.Hierarchy.StartsWith(...)
When, what you want is this:
(T)x => x.Layer.Hierarchy.StartsWith(...)
So, use this instead:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
Your logic should change a little though, since two .Where will generate an AND condition between them, and it seems like you want one of them to be true (StartsWith or Equals), so:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var orElseExp = Expression.OrElse(methodExpStartsWith, methodExpEquals);
var orElse = Expression.Lambda<Func<T, bool>>(orElseExp, parameterExp);
return query.Where(orElse);
PropertyInfo[] All_prop = Get_All_prop2(Model);
//------Get Properties has Value-----
foreach (PropertyInfo property in All_prop)
{
//--Check Has Value Property
if (property.GetValue(this, null) != null)
{
IQueryable<DAL_BankWebApp.TBUSER> Query = cntx.TBUSERs.Select(x => x);
//--Create Expression Of Property
ParameterExpression pe = Expression.Parameter(typeof(string), property.Name);
ConstantExpression Constant = Expression.Constant(property.GetConstantValue());
Expression Contain = Expression.Call(pe, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Constant);
//----Create Where
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { Query.ElementType },
Query.Expression,
Expression.Lambda<Func<string, bool>>(Contain, new ParameterExpression[] { pe }));
IQueryable<DAL_BankWebApp.TBUSER> results = Query.Provider.CreateQuery<DAL_BankWebApp.TBUSER>(whereCallExpression);
Result = results;
}
}
i get below error At => if (property.GetValue(this, null) != null)
base {"Object does not match target type."} System.ApplicationException {System.Reflection.TargetException}
i Solved :
if (property.GetValue(Model, null) != null)
I need make custom orderby for enum. I try use SwitchExpression:
public static IQueryable<T> MyOrderByEnum<T>(this IQueryable<T> source, string propName, Type enumType)
{
var type = typeof (T);
var property = type.GetProperty(propName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.Property(parameter, property);
var enumValues = Enum.GetValues(enumType);
var switchCases = new SwitchCase[enumValues.Length];
int i = 0;
foreach (var val in enumValues)
{
switchCases[i] = Expression.SwitchCase(
Expression.Constant(val.ToDisplay()),
Expression.Constant(val)
);
i++;
}
var switchExpr1 =
Expression.Switch(
propertyAccess,
Expression.Constant(""),
switchCases
);
var orderByExp1 = Expression.Lambda(switchExpr1, parameter);
MethodCallExpression resultExp = Expression.Call(typeof (Queryable), "OrderBy", new[] {type, orderByExp1.Body.Type}, source.Expression, orderByExp1);
return (IOrderedQueryable<T>) source.Provider.CreateQuery(resultExp);
}
But when I execute
filtered.MyOrderBy("field1", typeof(FieldState)).ToList();
I get error:
Unknown LINQ expression of type 'Switch'.
Is there another way to make order expression that will translate into sql construction "CASE WHEN ..."?
Try Expression.Condition (https://msdn.microsoft.com/en-us/library/bb340500%28v=vs.110%29.aspx)
I think that translates to CASE When if used in an anonymous projection
I am trying to write this groupQuery:
IQueryable<IGrouping<TKey, TEntity>> groupQuery;
...
IQueryable<TEntity> query2 = groupQuery.Select(x => x.FirstOrDefault());
as dynamically expression:
ParameterExpression param = Expression.Parameter(typeof(TEntity), "x");
IQueryable<TEntity> query2 = groupQuery.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
"Select",
new Type[] { typeof(TEntity), typeof(TKey)},
groupQuery.Expression,
Expression.Lambda(firstOrDefaultExpression, param)));
How to write firstOrDefaultExpression and how to complete this dynamically expression for same result as groupQuery.Select(x => x.FirstOrDefault())?
That should do it :
public IQueryable<TEntity> SelectFirst<TEntity,TKey>(IQueryable<IGrouping<TKey, TEntity>> groupQuery)
{
ParameterExpression param = Expression.Parameter(groupQuery.ElementType, "x");
var lambda = Expression.Lambda<Func<IGrouping<TKey, TEntity>,TEntity>>(
Expression.Call(typeof(Enumerable),"FirstOrDefault",new []{typeof(TEntity)},param),
param);
var select = Expression.Call(
typeof(Queryable),
"Select",
new []{groupQuery.ElementType,typeof(TEntity)},
groupQuery.Expression,lambda);
return groupQuery.Provider.CreateQuery<TEntity>(select);
}
And then you call that as :
SelectFirst(list.GroupBy(x=>[...]).AsQueryable());