Concatenate strings with Procedures inside Expression in Linq-to-Entities - linq

Let me start by asking, please don't answer "use AsEnumerable or ToList before", this would get the data into memory and then order. Since I intend to use the same code to apply filter dynamically, that would not be helpfull.
Having this class:
public class Employee {
public string Name;
public IEnumerable<string> Childs;
}
I need to be able to sort an IQueryable by Childs property.
Since I can't use string.Join directly, I was trying to make it dynamically using Expressions and combine it with a Stored Procedure that would return the names separeted by ",".
The problem is that I wasn't able to merge the procedure call inside the order expression.
The order expression that I'm using was taken from this:
Dynamic LINQ OrderBy on IEnumerable<T>
public static IOrderedQueryable<T> ApplyOrder<T>(this IQueryable<T> source, string propertyName, string methodName)
{
string[] properties = propertyName.Split('.');
Type type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type);
Expression expression = parameter;
PropertyInfo propertyInfo = null;
foreach (string property in properties)
{
propertyInfo = type.GetProperty(property, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (propertyInfo.PropertyType.GetInterfaces().Contains(typeof(IEnumerable)))
{
/*
The ideia was to call the procedure here and use it's value to order the source query
*/
}
else
{
expression = Expression.Property(expression, propertyInfo);
type = propertyInfo.PropertyType;
}
}
Type orderDelegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression orderLambda = Expression.Lambda(orderDelegateType, expression, parameter);
return (IOrderedQueryable<T>)typeof(Queryable).GetMethods().Single(method => method.Name == methodName && method.IsGenericMethodDefinition && method.GetGenericArguments().Length == 2 && method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, orderLambda });
}
Honestly I'm studying Expression for a week now and still have no idea where to begin with.

Related

How to write condition based on expression?

I am trying to write condition based on expression.
I have two classes Test1 and Test2.
public class Test1<TEntity, TProperty>
{
public IQueryable<TEntity> Test(
IQueryable<TEntity> queryable,
Expression<Func<TEntity, TProperty>> expression,
TProperty value
)
{
MemberExpression memberExpression = (MemberExpression)(expression.Body);
var propName = memberExpression.Member.Name;
// Cannot apply operator '>' to operands of type 'P' and 'P'
queryable = queryable.Where(e => EF.Property<TProperty>(e, propName) > value));
return queryable;
}
}
public class Test2<TEntity, TProperty>
where TProperty : IComparable
{
public IQueryable<TEntity> Test(
IQueryable<TEntity> queryable,
Expression<Func<TEntity, TProperty>> expression,
TProperty value
)
{
MemberExpression memberExpression = (MemberExpression)(expression.Body);
var propName = memberExpression.Member.Name;
// This one compiles
queryable = queryable.Where(e => EF.Property<TProperty>(e, propName).CompareTo(value) > 0);
return queryable;
}
}
First one (Test1) tries to compare values with > but it does not compile. Second one (Test2) declares generic type as IComparable and uses .CompareTo() method inside where condition. This one compiles but on runtime it throws:
The LINQ expression 'DbSet<SortableEntity>
.Where(s => s.IsDeleted == False)
.Where(s => EF.Property<long>((object)s, "Id").CompareTo((object)__value_0) > 0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()
Is it possible to somehow write custom condition based on expression? I want property/member expression to be passed by user and then decide what condition to apply to IQueryable.
Well, EF.Property is last thing that you have to use in such situation. You have property, even correct body - use this:
public class Test1<TEntity, TProperty>
{
public IQueryable<TEntity> Test(
IQueryable<TEntity> queryable,
Expression<Func<TEntity, TProperty>> expression,
TProperty value
)
{
var predicateLambda = Expression.Lambda<Func<TEntity, bool>>(
Expression.GreaterThan(expression.Body, Expression.Constant(value, typeof(TProperty))),
expression.Parameters[0]);
queryable = queryable.Where(predicateLambda);
return queryable;
}
}

LINQ IQueryable: Set 1 Parameter to accept Where Clause

sample code
public class program
{
public void sample()
{
var qry = repo.All(); // returns IQueryable //came from EF
var keyword = "al";
var result = qry.DynamicFilter(keyword, filterconditions);
// it should return all persons with 'al' in their name and nickname
}
public List<Expression<Func<Person, string, bool>>> filterconditions
{
get
{
var ret = new List<Expression<Func<Person, string, bool>>>();
ret.Add((m,n) => m.Name.Contains(n));
ret.Add((m,n) => m.Nickname.Contains(n));
return ret;
}
}
}
this is my extension
public static class linqExt
{
public static IQueryable<T> DynamicFilter<T>(this IQueryable<T> qry, string keyword, IEnumerable<Expression<Func<T, string, bool>>> conditions) where: T
{
var ret = qry;
foreach(var item in conditions)
{
ret = ret.Where(m => item.Compile()(m, keyword));
}
return ret;
}
}
is there any way to implement this without using the Compile/Invoke??
ret = ret.Where(m=> func.Compile()(m, keyword));??
the problem is that I can't use the Compile because the collection is an IQueryable and i can't convert it to IEnumarable because if I do that it will pull all the data from database.
I am thinking if I can use the METHODS and PROPERTIES of the EXPRESSION class
any guest? thanks
We have done something similar; but we use the PredicateBuilder class presented here: https://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/
var filter = PredicateBuilder.True<Person>()
.And(m => m.Name.Contains(n))
.And(m => m.Nickname.Contains(n));
Then you can just do qry.Where(filter) and it will apply your "dynamic" where clause. If this is LinqToEF this will cause the Where() clause to actually be generated and executed in SQL, which is probably want you want.
The PredicateBuilder is a very powerful class, and you can build some very elaborate clauses. When you need to do "ORs", be sure and "start" with a PredicateBuilder.False<Person>() statement.
You can also do conditional logic, and "append" statements to your filter, for example:
if (lookingForReallyLongNames == true)
{
filter = filter.And(m => m.Name.Length > 15);
}

C# Extension method to filter list

I found following extension method to filter list. I am very new to this hence I wanted to check if someone can help me. This method compare exact values but I want to use contains instead of exact compare. Any thoughts
public static IEnumerable<T> FilterByProperty<T>(this IEnumerable<T> source,string property,object value)
{
var propertyInfo = typeof(T).GetProperty(property);
return source.Where(p => propertyInfo.GetValue(p, null) == value);
}
If you want to change the equals == into Contains. try this:
public static IEnumerable<T> FilterByProperty<T>(this IEnumerable<T> source,string property,object value)
{
var propertyInfo = typeof(T).GetProperty(property);
return source.Where(p => propertyInfo
.GetValue(p, null)
.ToString() //be aware that this might be null
.Contains(value));
}

LINQ Is it possible to get a method name without a return type via LINQ expression trees?

I know it is possible to retrieve a property name or a method with a return type. But is it also possible to get a method name without a return type via LINQ expression trees?
Example: string methodname = GetMethodname(x=>x.GetUser());
---> results: "GetUser"
Absolutely - but you'll want a method signature like this:
public static string GetMethodName<T>(Expression<Action<T>> action)
(That means you'll need to specify the type argument when you call it, in order to use a lambda expression.)
Sample code:
using System;
using System.Linq.Expressions;
class Test
{
void Foo()
{
}
static void Main()
{
string method = GetMethodName<Test>(x => x.Foo());
Console.WriteLine(method);
}
static string GetMethodName<T>(Expression<Action<T>> action)
{
MethodCallExpression methodCall = action.Body as MethodCallExpression;
if (methodCall == null)
{
throw new ArgumentException("Only method calls are supported");
}
return methodCall.Method.Name;
}
}
You'll need something like this method:
public static string GetMethodName<T>(Expression<Action<T>> expression) {
if (expression.NodeType != ExpressionType.Lambda || expression.Body.NodeType != ExpressionType.Call)
return null;
MethodCallExpression methodCallExp = (MethodCallExpression) expression.Body;
return methodCallExp.Method.Name;
}
Call like this: GetMethodName<string>(s => s.ToLower()) will return "ToLower".

Dynamic Order (SQL ORDERBY) in LINQ CompiledQuery

how can I create a dynamic ORDERBY in my LINQ CompiledQuery (e.g. supply Order Field and Direction as parameters for the compiled query)?
I would do it this way, first all you really need is a way to access the property value by string on an object. You could use reflection, but its slow. So use this helper class approach which is based on the tests of http://stefan.rusek.org/Posts/LINQ-Expressions-as-Fast-Reflection-Invoke/3/
public static class LINQHelper
{
public static IComparable OrderByProperty<TClass>(TClass item,
string propertyName)
{
var t = Expression.Parameter(typeof(TClass), "t");
var prop = Expression.Property(t, propertyName);
var exp = Expression.Lambda(prop, t).Compile();
return (IComparable)exp.DynamicInvoke(item);
}
}
The in your code where you want your order by string of property name, in this example col1, you just do the following.
var myQuery = from i in Items
select i;
myQuery.OrderBy(i=>LINQHelper.OrderByProperty(i,"col1"));
Hope this helps.
I think I found it:
Check out this link. It will point you to the VS2008 code samples which contains a Dynamic Linq Query Library which contains the extension method below. This will allow you to go:
Object.OrderBy("ColumnName");
Here is the extension methods, but you may want the whole library.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) {
return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
}
public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) {
if (source == null) throw new ArgumentNullException("source");
if (ordering == null) throw new ArgumentNullException("ordering");
ParameterExpression[] parameters = new ParameterExpression[] {
Expression.Parameter(source.ElementType, "") };
ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering();
Expression queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (DynamicOrdering o in orderings) {
queryExpr = Expression.Call(
typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
new Type[] { source.ElementType, o.Selector.Type },
queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery(queryExpr);
}

Resources