Createing Expression<Func<IQueryable<T>, IOrderedQueryable<T>>>? - c#

I wanna create an Expression<Func<IQueryable<T>, IOrderedQueryable<T>>>, I have the following codes :
Expression selector = q => q.RegistrationDate
MethodInfo orderByMethodInfo = typeof(Queryable).GetMethods().First(method => method.Name == "OrderBy" && method.GetParameters().Count() == 2).MakeGenericMethod(argumentTypes);
MethodInfo orderByDescMethodInfo = typeof(Queryable).GetMethods().First(method => method.Name == "OrderByDescending" && method.GetParameters().Count() == 2).MakeGenericMethod(argumentTypes);
I'm gonna create c => c.OrderBy(q => q.RegistrationDate) or c => c.OrderByDescending(q => q.RegistrationDate) or generally something like c => c.OrderByDescending(q => q.RegistrationDate).ThenBy(q=>q.Name) from above codes.
Could you please guide how I can do it?

var paramExpr = Expression.Parameter(typeof(IQueryable<T>))
var orderByExpr = Expression.Call(orderByMethodInfo, paramExpr, selector);
var expr = Expression.Lambda<Func<IQueryable<T>, IOrderedQueryable<T>>>(orderByExpr, paramExpr);
Where T is the type with the RegistrationDate property in your selector expression.
You can get the queryable type from the argument type using MakeGenericType:
Type argType = typeof(DateTime);
Type queryableType = typeof(IQueryable<>).MakeGenericType(argType);

Related

ArgumentException when trying to translate efcore expression to expression trees

I am trying to build following query
_dbContext.Models.Where(m => m.Children.Any(c => c.Name.Contains("Foobar")))
using expression trees. I am not very familar with expression trees yet, quite frankly I find them challenging. What I've tried:
var toCompare = "Foobar";
var param = Expression.Parameter(typeof(T));
// Properties
var propertyLeft = Expression.Property(param, "Children");
// Trying to retrieve Name property of the Child type
var propertyRight = Expression.Property(Expression.Parameter(propertyLeft.Type.GetGenericArguments().Single()), "Name");
// Methods
var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
var any = typeof(Queryable).GetMethods()
.Where(m => m.Name == "Any")
.First(m => m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(string));
// Build c => c.Name.Contains(toCompare)
var expContains = Expression.Call(propertyRight, contains, Expression.Constant(toCompare, propertyRight.Type));
var expContainsLambda = Expression.Lambda<Func<T, bool>>(expContains, parameterExpression);
// Build m => m.Children.Any(...)
var expAny = Expression.Call(any, Expression.Constant(toCompare), expContainsLambda);
var expAnyLambda = Expression.Lambda<Func<Expression<Func<T, string>>, bool>>(expAny, parameterExpression);
// Build Models.Where(m => ...)
var expWhere = Expression.Lambda<Func<T, bool>>(expAnyLambda, param);
I am getting following error at expAny
System.ArgumentException: Expression of type 'System.String' cannot be used for parameter of type 'System.Linq.IQueryable`1[System.String]' of method 'Boolean Any[String](System.Linq.IQueryable`1[System.String], System.Linq.Expressions.Expression`1[System.Func`2[System.String,System.Boolean]])' (Parameter 'arg0')
Any ideas why? What changes are necessary to make it work?
Try the following:
var toCompare = "Foobar";
var param = Expression.Parameter(typeof(T), "m");
// m.Children
var propertyLeft = Expression.Property(param, "Children");
var childType = propertyLeft.Type.GetGenericArguments()[0];
var childParam = Expression.Parameter(childType, "c");
// c.Name
var propertyRight = Expression.Property(childParam, "Name");
// c => c.Name.Contains("Foobar")
var anyPredicate =
Expression.Lambda(
Expression.Call(propertyRight, nameof(string.Contains), Type.EmptyTypes, Expression.Constant(toCompare)),
childParam
)
// m.Children.Any(c => c.Name.Contains("Foobar"))
var anyCall = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new [] { childType }, propertyLeft, anyPredicate);
// m => m.Children.Any(c => c.Name.Contains("Foobar"))
var expWhere = Expression.Lambda<Func<T, bool>>(anyCall, param);
(Disclaimer: I am the author of the library in question.)
I've written a library that produces various string representations from expression trees, such as C# or VB code, or Dynamic LINQ. One of the representations is the factory methods needed to create an expression tree. For example, this:
Expression<Func<Model, bool>> expr = m => m.Children.Any(c => c.Name.Contains("Foobar"));
Console.WriteLine(
expr.ToString("Factory methods", "C#")
);
produces this (using simulated types Model and ModelChild:
// using static System.Linq.Expressions.Expression
var c = Parameter(
typeof(ModelChild),
"c"
);
var m = Parameter(
typeof(Model),
"m"
);
Lambda(
Call(
typeof(Queryable).GetMethod("Any", new[] { typeof(IQueryable<ModelChild>), typeof(Expression<Func<ModelChild, bool>>) }),
MakeMemberAccess(m,
typeof(Model).GetProperty("Children")
),
Quote(
Lambda(
Call(
MakeMemberAccess(c,
typeof(ModelChild).GetProperty("Name")
),
typeof(string).GetMethod("Contains", new[] { typeof(string) }),
Constant("Foobar")
),
c
)
)
),
m
)
which you can then use to customize for your own needs.

Pass Expression instead lambda

all!
I'll try to write extention for add mapping db column by defaults. I using linq2db
this is my method
public static void EntityWithDefaults<T>(this FluentMappingBuilder fluentMappingBuilder) {
fluentMappingBuilder.Entity<T>().HasTableName(typeof(T).Name);
var item = Expression.Parameter(typeof(T), typeof(T).Name);
foreach (var prop in typeof(T).GetProperties()) {
if (prop.Name == "ID")
fluentMappingBuilder.Entity<T>().Property(x => Expression.Property(item, prop.Name)).IsIdentity().IsPrimaryKey();
else
fluentMappingBuilder.Entity<T>().Property(x => Expression.Property(item, prop.Name));
}
}
it didn't work... but if I write like this - all is ok
fluentMappingBuilder.Entity<AppLogLong>()
.HasTableName("AppLog")
.Property(x => x.ID).IsPrimaryKey().IsIdentity()
.Property(x => x.ActionDate)
.Property(x => x.ActionId)
.Property(x => x.EmployeeId)
.Property(x => x.RequestType);
I think my problem is wrong expressions for properties. Could you help me, plz?
Thanks!
x => x.ID is not the same as x => Expression.Property(item, "ID").
What you want to do is probably:
foreach (var prop in typeof(T).GetProperties()) {
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, prop);
var cast = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<T, object>>(cast, parameter);
if (prop.Name == "ID")
fluentMappingBuilder.Entity<T>().Property(lambda).IsIdentity().IsPrimaryKey();
else
fluentMappingBuilder.Entity<T>().Property(lambda);
}
That is, we have to construct the entire LambdaExpression ourselves.

Generic predicate to implement searching by any string property

Is there possibility to apply searching by any string property of entity? I need to build predicate to use it in LINQ query to database.
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
foreach (var item in stringPropertyNames)
{
// here some code to build Predicate corresponding to sql LIKE statement
}
UPD:
here is working code:
public static IEnumerable<T> ContainsInAnyString<T>(IQueryable<T> collection, string query)
{
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
var item = Expression.Parameter(typeof(T), "item");
var searchArgument = Expression.Constant(query);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
foreach (var name in stringPropertyNames)
{
var currentProp = Expression.Property(item, name);
var startsWithDishExpr = Expression.Call(currentProp, method, searchArgument);
var lambda = Expression.Lambda<Func<T, bool>>(startsWithDishExpr, item);
collection = collection.Where(lambda);
}
return collection;
}
Hope it would be helpful for someone.
It really is rather simple:
var entityParameter = Expression.Parameter(typeof(T), "entity");
return
Expression.Lambda<Func<T, bool>>
(
Expression.Equal
(
Expression.MakeMemberAccess(entityParameter, propertyInfo),
Expression.Constant(valueToSearchBy)
),
entityParameter
);
Of course, this does A == B. If you want A like '%' + B + '%' instead, you'll have to change the Expression.Equal to string.Contains. I'll leave that as an excercise, since you haven't shown any of your work yet :)

Expression Tree for a Collection

I can do the following manually but need to build it up using System.Linq.Expressions.
.Where(x => x.OrganizationPersonRoles.Any(o => o.OrganizationId == value))
I can get it built up to the .Any and know I need to do an Expression.Call for the Any method but don't know how to build up the inner lambda (o => o.OrganizationId == value).
Expression exp = Expression.Parameter(typeof(T), "x");
Type type = typeof(T);
PropertyInfo pi = type.GetProperty("OrganizationPersonRoles");
exp = Expression.Property(exp, pi);
var lambda = Expression.Lambda(exp, arg);
Edit:
It's the ".Any" part I can't figure out how to build the expression. "OrganizationPersonRoles" is a collection on "Person". Something like:
var anyMethod = typeof(Queryable).GetMethods()
.Where(m => m.Name == "Any")
.Single(m => m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(string));
var body = Expression.Call(exp, anyMethod, "Expression For Inner Lambda");
var lambda = Expression.Lambda(body, arg);
You can construct the lambda o => o.OrganizationId == value as follows.
int value = 5;
var xParameter = Expression.Parameter(typeof(T), "x");
var oParameter = Expression.Parameter(typeof(Organization), "o");
var expression =
Expression.Lambda(
Expression.Call(
typeof(Queryable), "Any", new[] { typeof(Organization) },
Expression.Property(xParameter, "OrganizationPersonRoles"),
Expression.Constant(
Expression.Lambda(
Expression.Equal(
Expression.Property(oParameter, "OrganizationId"),
Expression.Constant(value, typeof(int))),
oParameter),
typeof(Expression<Func<Organization, bool>>))),
xParameter);

how can I extend dlinq class to have empty strings last in a sort [duplicate]

I try to make my custom orderby extension method, i successfully worked my code but in addition i want to list null or empty or zero values last in result, anyone can help me about that issue ?
Here is my extension method to orderby
public static IQueryable<T> OrderBy<T>(this IQueryable<T> q, string SortField, bool isAsc)
{
//var nullExpr = Expression.Constant(null, typeof(T));
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = isAsc ? "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);
}
Thanks in advance
The simplest way is to use
OrderBy(e => String.IsNullOrEmpty(e.TeamName)
This doesn't require any extension method or custom IComparer implementation etc.
var entries = repository.Race.Where(e => e.EventId == id)
.OrderBy(e => String.IsNullOrEmpty(e.TeamName))
.ThenBy(e => e.LastName)
.ThenBy(e => e.FirstName);
Without using an extension method....
Create a custom IComparer<string> to check the empty values before using the default String.Compare. The first checks will return -1 instead of 1 or 1 instead of -1, if using the standard string comparison.
/// <summary>
/// Returns -1 instead of 1 if y is IsNullOrEmpty when x is Not.
/// </summary>
public class EmptyStringsAreLast : IComparer<string>
{
public int Compare(string x, string y)
{
if (String.IsNullOrEmpty(y) && !String.IsNullOrEmpty(x))
{
return -1;
}
else if (!String.IsNullOrEmpty(y) && String.IsNullOrEmpty(x))
{
return 1;
}
else
{
return String.Compare(x, y);
}
}
}
Pass your EmptyStringsAreLast comparer into the OrderBy of Lambda expression. In this solution teams who have entered the race should appear alphabetical order, but the unaffiliated race entries should appear at then end.
var entries = repository.Race.Where(e => e.EventId == id)
.OrderBy(e => e.TeamName, new EmptyStringsAreLast())
.ThenBy(e => e.LastName)
.ThenBy(e => e.FirstName);
This answer is perhaps what you were originally looking for - using your generic extension method:
public static IQueryable<T> OrderByFieldNullsLast<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
//We are rebuilding .OrderByDescending(p => p.SortField.HasValue).ThenBy(p => p.SortField)
//i.e. sort first by whether sortfield has a value, then by sortfield asc or sortfield desc
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, SortField);
var hasValue = Expression.Property(prop, "HasValue");
var exp = Expression.Lambda(hasValue, param);
string method = "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
//now do the ThenBy bit,sending in the above expression to the Expression.Call
exp = Expression.Lambda(prop, param);
types = new Type[] { q.ElementType, exp.Body.Type };
method = Ascending ? "ThenBy" : "ThenByDescending";
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types,orderByCallExpression, exp);
return q.Provider.CreateQuery<T>(ThenByCallExpression);
}
Building on Dave Anson's answer, you can user Comparer.Create() to create the Comparer from a lambda. Here's an example that sorts unsorted by its myString string fields, with null or empty strings appearing last.
var sorted = unsorted.OrderBy(x => x.myString, Comparer<string>.Create((x, y) => {
if ( string.IsNullOrEmpty(y) && !string.IsNullOrEmpty(x)) return -1;
else if (!string.IsNullOrEmpty(y) && string.IsNullOrEmpty(x)) return +1;
else return string.Compare(x, y);
}))
(To put them first, switch the signs on the 1 constants)
it works for me:
private static IQueryable<T> GetOrderQuery<T>(this IQueryable<T> q, BaseFilterCollection filter)
{
q = q.OrderBy(GetExpression<T>(filter.SortField));
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, filter.SortField);
var exp = Expression.Lambda(prop, param);
string method = filter.SortDirection == SortDirectionType.Asc ? "ThenBy" : "ThenByDescending";
Type[] types = { q.ElementType, exp.Body.Type };
var rs = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(rs);
}
private static Expression<Func<T, bool>> GetExpression<T>(string sortField)
{
ParameterExpression param = Expression.Parameter(typeof(T), "p");
Expression prop = Expression.Property(param, sortField);
var info = typeof(T).GetProperty(sortField, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Expression exp = Expression.Equal(prop, info.PropertyType.IsValueType
? Expression.Constant(Activator.CreateInstance(info.PropertyType))
: Expression.Constant(null));
return Expression.Lambda<Func<T, bool>>(exp, param);
}
You dont need to complicate, the easiest way is to do something like this:
YourList.OrderByDescending(x => string.IsNullOrEmpty(x.value)
Use OrderByDescending or OrderBy depending on if you want to see empty strings in the beginning or last.
Regards

Categories