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());
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 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
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?
I need to create an expression tree, which I can pass along as predicate argument in Where clause to Linq To Enities query.
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate);
Expression tree must be equivalent to where clause specified as below:
var query = context.Products.Select(product =>
new { product.Name, product.Color });
var arr = "Red;Black".Split(';');
query = query.Where(obj => arr.Contains(obj.Color));
Googled it out!
var paramExpr = Expression.Parameter(typeof(TSource), "src");
var memberExpr = (MemberExpression)property.Body;
List<Expression> arrayInits = new List<Expression>();
var arrExpr = Expression.Constant(((string)values[0]).Split(new char[] { ';' }));
MethodInfo containsMethod = typeof(ICollection<string>).GetMethod("Contains");
var containsExpression = Expression.Call(null, contains, arrExpr, memberExpr);
var containsLambda = Expression.Lambda<Func<TSource, bool>>(containsExpression, property.Parameters);
And it works!
public static Expression<Func<T, bool>> CreateContainsExpression<T>(T obj, string[] array)
{
var paramExpr = Expression.Parameter(typeof(T), "obj");
var arrExpr = Expression.Constant(array);
var colourPropExpr = Expression.Property(paramExpr, "Color");
MethodInfo containsMethod = typeof(ICollection<string>).GetMethod("Contains");
var containsExpr = Expression.Call(arrExpr, containsMethod, colourPropExpr);
return Expression.Lambda<Func<T, bool>>(containsExpr, paramExpr);
}
i want to build a generic search window using linq to sql.
This is what i was trying to do:
class SearchWindow<T> : Form : Where T: class
{
public SearchWindow(Func<T, string> codeSelector,
Func<T, string> nameSelector)
{
var db = new DataContext();
var table = db.GetTable<T>();
var query = from item in table where
codeSelector(item).Contains(someText) &&
nameSelector(item).Contains(someOtherText)
select item;
}
}
And i was trying to use it like:
var searchWindow = new SearchWindow<SomeTable>(x => x.CodeColumn,
y => y.NameColumn).Show();
Bud saddly that doesn't work, i read about expression trees so i tried to do that with them, and i got:
public SearchWindow(codeColumn, nameColumn)
{
Table<T> table = db.GetTable<T>();
var instanceParameter = Expression.Parameter(typeof(T), "instance");
var methodInfo = typeof(string).GetMethod("Contains",
new Type[] { typeof(string) });
var codigoExpression = Expression.Call(Expression.Property(instanceParameter,
codeColumn),
methodInfo,
Expression.Constant("someText",
typeof(string)));
var nombreExpression = Expression.Call(Expression.Property(instanceParameter,
nameColumn),
methodInfo,
Expression.Constant("someOtherText",
typeof(string)));
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.And(codigoExpression, nombreExpression), instanceParameter);
var query = table.Where(predicate);
}
And to use it i need to do:
new SearchWindow<SomeTable>("codeColumn", "nameColumn");
But i don't like the approach to need to enter the column names as a string, is there any way to do it in a fashion similar to my first approach (in order to have intellisense and strong typing)?
Thank you for your help.
Untested, but something like:
static IQueryable<T> Search<T>(
IQueryable<T> source,
Expression<Func<T, string>> codeSelector,
Expression<Func<T, string>> nameSelector,
string code, string name)
{
var row = Expression.Parameter(typeof(T), "row");
var body = Expression.AndAlso(
Expression.Call(
Expression.Invoke(codeSelector, row),
"Contains", null,
Expression.Constant(code, typeof(string))),
Expression.Call(
Expression.Invoke(nameSelector, row),
"Contains", null,
Expression.Constant(name, typeof(string))));
var lambda = Expression.Lambda<Func<T, bool>>(body, row);
return source.Where(lambda);
}
You pass in your table (GetTable<T>) as the source, and lambdas to indicate the columns (x => x.CodeColumn / y => y.NameColumn etc).
Update; tested on LINQ-to-Objects, I'm hopeful it'll work on LINQ-to-SQL as well:
var data = new[] {
new { Code = "abc", Name = "def"},
new { Code = "bcd", Name = "efg"},
new { Code = "ghi", Name = "jkl"}
}.AsQueryable();
var filtered = Search(data, x => x.Code, x => x.Name, "b", "f");
var arr = filtered.ToArray();
Use PredicateBuilder- it'll do the heavy lifting for you.