Dynamic GroupBy using expression in .net core - c#

I wrote an extension method that makes an expression based on condition, but when the condition is groupby, the result is order by !!!
what am I wrong?
here is my method:
public static IQueryable<T> NewWhere<T, U>(this IQueryable<T> source, string prop, U value, string condition)
{
MethodInfo method;
Expression<Func<T, bool>> lambda = null;
Expression body = null;
string groupSelector = null;
var type = typeof(T);
var parameter = Expression.Parameter(type, "p");
var property = Expression.Property(parameter, prop);
var constant = Expression.Constant(value, typeof(U));
if (condition == "GreaterThan")
body = Expression.GreaterThan(property, constant);
else if (condition == "LessThan")
body = Expression.LessThan(property, constant);
else if (condition == "Equals")
body = Expression.Equal(property, constant);
//For implement sql like command we need them
else if (condition == "StartsWith") {
method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
body = Expression.Call(property, method, constant);
}
else if (condition == "EndsWith")
{
method = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
body = Expression.Call(property, method, constant);
}
else if (condition == "Contains")
{
method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
body = Expression.Call(property, method, constant);
}
//For implement sql like command we need them
//group by only one field
if (condition == "GroupBy")
groupSelector = prop;
else
lambda = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
//return the grouped by result or not grouped by
if (groupSelector != null)
{
var selectorExp = Expression.Lambda<Func<T, U>>(property, new ParameterExpression[] { parameter });
source = source.GroupBy(selectorExp).SelectMany(g => g);
//source.GroupBy(e => e.GetType().GetProperty(groupSelector)).SelectMany(gr => gr);
}
else
source = source.Where(lambda);
return source;
}
but when i run the mthod with GroupBy condition, the result is:
SELECT [e].[Id], [e].[Year]
FROM [org].[Data] AS [e]
ORDER BY [e].[Year]
i don't know why its happened?

TL;DR;: Entitity Framework uses the query because it is the most efficient way to get what you want.
What Entity Framework does is to translate your LINQ query into SQL. Check this line of your code:
source = source.GroupBy(selectorExp).SelectMany(g => g);
You are grouping (probably by year) and then you select all the items of the group. You are actually not requesting a grouped result set, you are expecting all items in all groups in a single flat result set. If EF would first request the groups and then request the group items, it would first have to select all groups:
SELECT [e].[Year]
FROM [org].[Data] AS [e]
GROUP BY [e].[Year]
Then it would have to get the group items in one query for each group:
SELECT [e].[Id]
FROM [org].[Data] AS [e]
WHERE [e].[Year] = --Value
That of course would be very inefficient (especially since you flatten the list anyway with SelectMany), so EF will just get the values ordered by your grouping predicate and group them during query execution (or in this case not group them at all, since you are requesting a flat list). Once the query is executed, EF can just start at the top of the result set and start a new group every time it encounters a new year.
When you are using EF queries, you have to accept that you won't have control over your SQL. If you want that, create stored procedures and run them from EF.

Related

How to use a variable when making a lambda expression using Expression.Lambda<Func<>>()

I have a linq query which is as follows:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select(lambda)
.ToList();
I use lambda because i have a variable in my select statement. The lambda:
var wantedData = "Adress";
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, wantedData);
var lambda = Expression.Lambda<Func<Person, Adress>>(body, x);
The query will return the addresses for person with id 123. Let's say instead of the addresses I would like to recieve all subscriptions this person has. Only setting wantedData = "Subscription" will not work, because of the "Adress" in the lambda statement. So I'm looking for a way to use a variable to change "Adress" in the lambda statement.
I tried the following, which obviously does not work.
var lambda = Expression.Lambda<Func<Person, wantedData>>(body, x);
Is there a way to do this?
Ok, I managed to create a generic query method that takes an Expression as parameter.
EDIT: This will receive a string value and will try to match it with an existing property on your Person class.
private IEnumerable<TResult> GetPersonData<TResult>(Expression<Func<Person, TResult>> selectExpression)
{
return _dbContext.Persons
// .Where(filter)
.Select(selectExpression)
.ToList();
}
public IEnumerable<object> GetData(string dataPropertyName)
{
switch(dataPropertyName) {
case nameof(Person.Address): return GetPersonData(p => p.Address);
case nameof(Person.Subscription): return GetPersonData(p => p.Subscription);
// other cases
default: throw new InvalidArgumentException("Invalid property name");
}
}
note that this code is just an example written on the spot and it might not work directly with copy-paste
I've made something similar to an OrderBy and tweak it a little for your select. This only works if you know the return type.
You can create an extension method with the following code:
public static IQueryable<TResult> Select<T,TResult>(this IQueryable<T> source, string propertyName)
{
var prop = typeof(T).GetProperties()
.FirstOrDefault(x => x.Name.ToUpper() == propertyName.ToUpper());
ParameterExpression pe = Expression.Parameter(typeof(T), "x");
MemberExpression body = Expression.Property(pe, prop.Name);
LambdaExpression lambda = Expression.Lambda(body, new ParameterExpression[] { pe });
MethodCallExpression selectCallExpression = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { source.ElementType, prop.PropertyType },
source.Expression,
lambda);
return source.Provider.CreateQuery<TResult>(selectCallExpression);
}
That way, you can use it like:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select<Person,string>(wantedData)
.ToList();

Dynamically Build EF Query

I am working on a dynamic query solution for a project. I want to avoid a bunch of if/else or switch statements just to change the [DynamicFieldName] part of these queries.
IQueryable<MyDataType> allItems = (from item in Context.MyDataTypes select item);
foreach (QueryEntry currentEntry in query.Fields)
{
allItems = allItems.Where(item => item.[DynamicFieldName] == currentEntry.Value);
}
The user gets to build the query via the GUI that has a variable number of fields. In the end, they will also have a variety of comparisons to choose from (Less than, greater than, equal, contains, etc.) that vary by data type.
What method can I use to build this programatically in a nice reusable fashion?
Have a look at this code:
public static class CustomQueryBuilder
{
//todo: add more operations
public enum Operator
{
Equal = 0,
GreaterThan = 1,
LesserThan = 2
}
public static IQueryable<T> Where<T>(this IQueryable<T> query, string property, Operator operation, object value)
{
//it's an item which property we are referring to
ParameterExpression parameter = Expression.Parameter(typeof(T));
//this stands for "item.property"
Expression prop = Expression.Property(parameter, property);
//wrapping our value to use it in lambda
ConstantExpression constant = Expression.Constant(value);
Expression expression;
//creating the operation
//todo: add more cases
switch (operation)
{
case Operator.Equal:
expression = Expression.Equal(prop, constant);
break;
case Operator.GreaterThan:
expression = Expression.GreaterThan(prop, constant);
break;
case Operator.LesserThan:
expression = Expression.LessThan(prop, constant);
break;
default:
throw new ArgumentException("Invalid operation specified");
}
//create lambda ready to use in queries
var lambda = Expression.Lambda<Func<T, bool>>(expression, parameter);
return query.Where(lambda);
}
}
Usage
var users = context
.Users
.Where("Name", CustomQueryBuilder.Operator.Equal, "User")
.ToList();
Which is equal to
var users = context
.Users
.Where(u => u.Name == "User")
.ToList();

Any shorter solution for select - from - where LINQ?

I have the search function. and I want to select all value in my Book table which contain _searchdata but I dont know how to express at "where" with the short code instead of listing all items of Table like this:
(I just get some items for example, it contains about 100 items like Booktitle, Author, Genre... i dont want to specify it because it's so long)
public void SearchAny(string _searchdata)
{
var searchAnyInDB = from Book x in BookDB.Books
where (x.BookTitle.Contains(_searchdata)
|| x.Author.Contains(_searchdata)
|| x.Genre.Contains(_searchdata))
select x;
DataSearch.Clear();
DataSearch = new ObservableCollection<Book>(searchAnyInDB);
}
Because LINQ to Entities and LINQ to SQL both use Expression<Func<TSource, bool>> as IQueryable.Where extension method parameter, you can use reflection to create that Expression during compile type and generate all there || instead of typing them into your source code.
Would be something like:
var searchAnyInDB = from Book x in BookDB.Books
where (GetWhereExpression<Book>(_searchdata))
select x;
And GetWhereExpression<TSource> method:
static Expression<Func<TSource, bool>> GetWhereExpression<TSource>(string value)
{
var param = Expression.Parameter(typeof(TSource));
var val = Expression.Constant(value);
var expression = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
foreach(var prop in typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if(prop.PropertyType == typeof(string))
{
expression = Expression.OrElse(expression,
Expression.Call(
Expression.Property(param, prop),
"Contains",
null,
val
)
);
}
}
return Expression.Lambda<Func<TSource, bool>>(expression, param);
}
You still can do better then that, ex. remembering the expression for type to prevent using reflection every time you need to execute the query with different search texts, etc. But it should give you an idea where to go.

Dynamic linq query expression tree for sql IN clause using Entity framework

I want to create a dynamic linq expression for sql IN clause in EF 6.0 with code first approch. Note that i am new to Expressions. What i want to achive is
select * from Courses where CourseId in (1, 2, 3, 4)
//CourseId is integer
The normal linq query looks like this. But i want to query it dynamically
string[] ids = new string[]{"1", "2", "3", "4"};
var courselist = DBEntities.Courses.Where(c => ids.Contains(SqlFunctions.StringConvert((decimal?)c.CourseId)))
There are two ways to make dynamic expression.
1) one ways is to loop through ids and make expressions
The below code will create the following expression in debug view
{f => ((StringConvert(Convert(f.CourseId)).Equals("23") Or StringConvert(Convert(f.CourseId)).Equals("2")) Or StringConvert(Convert(f.CourseId)).Equals("1"))}
Dynamic Expression is
var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId");
MethodInfo mi = null;
MethodCallExpression mce = null;
if (property.Type == typeof(int))
{
var castProperty = Expression.Convert(property, typeof(double?));
var t = Expression.Parameter(typeof(SqlFunctions), "SqlFunctions");
mi = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(double?) });
mce = Expression.Call(null,mi, castProperty);
}
mi = typeof(string).GetMethod("Equals", new Type[]{ typeof(string)});
BinaryExpression bex = null;
if (values.Length <= 1)
{
return Expression.Lambda<Func<T, bool>>(Expression.Call(mce, mi, Expression.Constant(values[0]), param));
}
//var exp1 = Expression.Call(mce, mi, Expression.Constant(values[0]));
for (int i = 0; i < values.Length; i++)
{
if (bex == null)
{
bex = Expression.Or(Expression.Call(mce, mi, Expression.Constant(values[i])), Expression.Call(mce, mi, Expression.Constant(values[i + 1])));
i++;
}
else
bex = Expression.Or(bex, Expression.Call(mce, mi, Expression.Constant(values[i])));
}//End of for loop
return Expression.Lambda<Func<T, bool>>(bex, param);
2) The 2nd way that i tried (debug view)
{f => val.Contains("23")} //val is parameter of values above
The dynamic expression for above that i tried is
var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId");
var micontain = typeof(Enumerable).GetMethods().Where(m => m.Name == "Contains" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(string));
var mc = Expression.Call(micontain, Expression.Parameter(values.GetType(), "val"), Expression.Constant("2"));//NOTE: I haven't use CourseId for now as i am getting conversion error
return Expression.Lambda<Func<T, bool>>(mc, param);
I get the following errors
LINQ to Entities does not recognize the method 'System.String StringConvert(System.Nullable`1[System.Double])' method, and this
method cannot be translated into a store expression when i use the
first methodology. I know i can't use ToString with EF thats why I used SqlFunctions but it is not working for me.
The parameter 'val' was not bound in the specified LINQ to Entities query expression using 2nd methodology
I am trying this from last 4 days. I googled it but didn't find any suitable solution. Please help me.
After a lot of struggle I found solution to my question.
I want to achieve this sql query
select * from Courses where CourseId in (1, 2, 3, 4)
Using Linq to Entities, but I want to pass in(1,2,3,4) list dynamically to linq query. I created an Extension class for that purpose.
public static class LinqExtensions
{
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> In<T, TValue>(this Expression<Func<T, bool>> predicate,string propertyName, List<TValue> values)
{
var param = predicate.Parameters.Single();
MemberExpression property = Expression.PropertyOrField(param, propertyName);
var micontain = typeof(List<TValue>).GetMethod("Contains");
var mc = Expression.Call(Expression.Constant(values), micontain, property);
return Expression.Lambda<Func<T, bool>>(mc, param);
}
}
Use of LinqExtensions
var pred = LinqExtensions.False<Course>(); //You can chain In function like LinqExtensions.False<Course>().In<Course, int>("CourseId", inList);
var inList= new List<int>(){1, 2, 3}; //Keep in mind the list must be of same type of the Property that will be compared with. In my case CourseId is integer so the in List have integer values
pred =pred.In<Course, int>("CourseId", inList); //TValue is int. As CourseId is of type int.
var data = MyEntities.Courses.Where(pred);
I hope this might be beneficial for some one
have you seen the type of
var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId)))
above statement would not return actual list of courses. The query is not executed yet. It just returns IQuereable. The query is executed when you actually call .ToList() method on it
so, your solution is..
Create array of IDs using for loop and then simply run the below query
var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId))).ToList()

Dynamic "WHERE IN" on IQueryable (linq to SQL)

I have a LINQ to SQL query returning rows from a table into an IQueryable object.
IQueryable<MyClass> items = from table in DBContext.MyTable
select new MyClass
{
ID = table.ID,
Col1 = table.Col1,
Col2 = table.Col2
}
I then want to perform a SQL "WHERE ... IN ...." query on the results. This works fine using the following. (return results with id's ID1 ID2 or ID3)
sQuery = "ID1,ID2,ID3";
string[] aSearch = sQuery.Split(',');
items = items.Where(i => aSearch.Contains(i.ID));
What I would like to be able to do, is perform the same operation, but not have to specify the i.ID part. So if I have the string of the field name I want to apply the "WHERE IN" clause to, how can I use this in the .Contains() method?
There's a couple of ways to do this. One way is to use Dynamic Linq. Another way is to use Predicate Builder.
Your have to build an expression tree. It will end up looking like this (partial code, will not compile). This one does both contains and equals. Used in this project: http://weblogs.asp.net/rajbk/archive/2010/04/15/asp-net-mvc-paging-sorting-filtering-a-list-using-modelmetadata.aspx
var param = Expression.Parameter(filterType, propertyName);
var left = Expression.Property(param, filterType.GetProperty(propertyName));
var right = Expression.Constant(propertyValue, modelMetaData.ModelType);
LambdaExpression predicate = null;
if (searchFilterAttribute.FilterType == FilterType.Contains)
{
var methodContains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var filterContains = Expression.Call(left, methodContains, right);
predicate = Expression.Lambda(filterContains, param);
}
else
{
var expr = Expression.Equal(left, right);
predicate = Expression.Lambda(expr, param);
}
var expression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryable.ElementType },
queryable.Expression,
predicate);
queryable = queryable.Provider.CreateQuery<T>(expression);
I may rewrite this into a reusable extension method (it is too specific to that project at the moment) and blog about it.

Categories

Resources