I have the need to construct an IOrderedQueryable to apply as an orderby statement but I'm stuck at how to do this with i'm given a string that is a comma delimited string of fields.
I can iterate over the string and break the string into the individual pieces, but I dont know what i need to do to build these pieces into an IOrderedQueryable.
// the orderBy string is separated by ",", so we split it.
var orderByAfterSplit = orderBy.Split(',');
// apply each orderby clause in reverse order - otherwise, the
// IQueryable will be ordered in the wrong order
foreach (var orderByClause in orderByAfterSplit.Reverse())
{
// trim the orderByClause, as it might contain leading
// or trailing spaces. Can't trim the var in foreach,
// so use another var.
var trimmedOrderByClause = orderByClause.Trim();
// if the sort option ends with with " desc", we order
// descending, otherwise ascending
var orderDescending = trimmedOrderByClause.EndsWith(" desc");
// remove " asc" or " desc" from the orderByClause, so we
// get the property name to look for in the mapping dictionary
var indexOfFirstSpace = trimmedOrderByClause.IndexOf(" ");
var propertyName = indexOfFirstSpace == -1 ?
trimmedOrderByClause : trimmedOrderByClause.Remove(indexOfFirstSpace);
// HERE IS MY PROBLEM
// what needs to be here?
}
Can anyone help?
an example of the orderBy string may be
name
name desc
name,code
name,code desc,value
public static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
PropertyInfo pi = type.GetProperty(prop);
if (pi == null)
pi = type.GetProperties().FirstOrDefault(x => x.Name.ToLower() == prop.ToLower());
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = 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, lambda });
return (IOrderedQueryable<T>)result;
}
ApplyOrder(source, property, "OrderBy");
ApplyOrder(source, property, "OrderByDescending")
ApplyOrder(source, property, "ThenBy");
ApplyOrder(source, property, "ThenByDescending");
Related
I'm working with an IQueryable<SomeRandomObject> that is pulled using an EF Core 3.1 data context.
I'm pretty sure I can dynamically build a predicate for .Where() so that I can pass a string in for what column, and what value.
Of course this doesn't work, but some pseudo-code might be:
IQueryable myQueryable = stuffFromContext;
var columnName = "memberid";
var searchValue = "1234";
var results = myQueryable.Where(x=> someMagicColumnFunction(columnName, searchvalue))
I've only done research at this point, and predicate building is not my area of expertise.
Can someone help me create a function that I can pass in the parameters my IQueryable, a string representing the column name, and a string for the search (full equality for now, no 'like').
I'd love to see how this is done. I can't find a solid example anywhere on how to do something small like this. Most of the examples are everything and the kitchen sink!
Assuming the type of the rows in myQueryable are TQueryable then you can create a myQueryable specific function to generate the lambda:
Expression<Func<TQueryable, bool>> EqualsFilter<TCol>(string columnName, TCol searchValue) {
// build x => x.{columnName} == searchValue
// (TQueryable x)
var xParam = Expression.Parameter(typeof(TQueryable), "x");
// x.{columnName}
var colExpr = Expression.Property(xParam, columnName);
// {searchValue}
var constExpr = Expression.Constant(searchValue);
// x.{columnName} == {searchValue}
var lambdaBody = Expression.MakeBinary(ExpressionType.Equal, colExpr, constExpr);
// (TQueryable x) => x.{columnName} == {searchValue}
var lambda = Expression.Lambda<Func<TQueryable, bool>>(lambdaBody, xParam);
return lambda;
}
Once you have the method, you can use it like:
var myQueryable = stuffFromContext;
var columnName = "memberid";
var searchValue = "1234";
var results = myQueryable.Where(EqualsFilter(columnName, searchvalue));
However, if myQueryable has a complex or anonymous type (because of a Select or Join) you need to replace the Where as C# can only infer types from parameters, so you need the myQueryable parameter to get the entity type you are filtering. Using a generic version of EqualsFilter as a helper method, you have:
public static class IQueryableExt {
static Expression<Func<T, bool>> EqualsFilter<T, TCol>(string columnName, TCol searchValue) {
// build x => x.{columnName} == searchValue
// (T x)
var xParam = Expression.Parameter(typeof(Accounts), "x");
// x.{columnName}
var colExpr = Expression.Property(xParam, columnName);
// {searchValue}
var constExpr = Expression.Constant(searchValue);
// x.{columnName} == {searchValue}
var lambdaBody = Expression.MakeBinary(ExpressionType.Equal, colExpr, constExpr);
// (T x) => x.{columnName} == {searchValue}
var lambda = Expression.Lambda<Func<T, bool>>(lambdaBody, xParam);
return lambda;
}
public static IQueryable<T> WhereColumnEquals<T, TCol>(this IQueryable<T> src, string columnName, TCol searchValue)
=> src.Where(EqualsFilter<T, TCol>(columnName, searchValue));
}
Which you can now use like:
var myQueryable = stuffFromContext;
var columnName = "memberid";
var searchValue = "1234";
var results = myQueryable.WhereColumnEquals(columnName, searchvalue);
I hope somebody can guide and help me with this. We have an inherited project that uses ExpressionHelper class. Basically, this Expression Helper will return an IQueryable that build a dynamic query base on the search term that the user provided.
For example, I have the below code where I pass 2 search terms.
IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();;
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "John" }
};
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var obj = ExpressionHelper.Parameter<TEntity>();
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider
.GetComparison(left, searchTerm.Operator, right);
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper
.GetLambda<TEntity, bool>(obj, comparisonExpression);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
With the code above and using the below ExpressionHelper class, this generate the below SQL query when I check using SQLProfiler. Please notice the AND in the query. What I actually what is OR.
Constructed QUERY in SQL Profiler
SELECT
[Extent1].[FirstName] AS [FirstName],
FROM [dbo].[tblUser] AS [Extent1]
WHERE ([Extent1].[Conatact1] = N'Bob') AND ([Extent1].[Contact2] = N'John')
ExpressionHelper.cs
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo[] QueryableMethods = typeof(Queryable)
.GetMethods()
.ToArray();
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
{
var whereMethodBuilder = QueryableMethods
.First(x => x.Name == "Where" && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(T) });
return (IQueryable<T>)whereMethodBuilder
.Invoke(null, new object[] { query, predicate });
}
public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
IQueryable<TEntity> modifiedQuery,
bool useThenBy,
bool descending,
Type propertyType,
LambdaExpression keySelector)
{
var methodName = "OrderBy";
if (useThenBy) methodName = "ThenBy";
if (descending) methodName += "Descending";
var method = QueryableMethods
.First(x => x.Name == methodName && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(TEntity), propertyType });
return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
}
}
I have hard time understanding on how the query was created and how do I change it to become OR in the created query.
Hope someone can guide me and point to the right direction. Thank you!
Add to SearchTerm a new property (C# 6.0 syntax here):
// This is quite wrong. We should have an enum here, but Operator is
// done as a string, so I'm maintaining the "style"
// Supported LogicalConnector: and, or
public string LogicalConnector { get; set; } = "and";
}
Then:
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
Expression comparisonExpressions = null;
var obj = ExpressionHelper.Parameter<TEntity>();
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);
if (comparisonExpressions == null)
{
comparisonExpressions = comparisonExpression;
}
else if (searchTerm.LogicalConnector == "and")
{
comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
}
else if (searchTerm.LogicalConnector == "or")
{
comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
}
else
{
throw new NotSupportedException(searchTerm.LogicalConnector);
}
}
if (comparisonExpressions != null)
{
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
return modifiedQuery;
}
Use it like:
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};
IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
Note that there is no way in this code to explicitly set brackets, that will be implicitly set as:
(((A opB b) opC C) opD D)
Where A, B, C, D are the SearchTerm[0], SearchTerm[1], SearchTerm[2], SearchTerm[3] and opB, opC, opD are the operators defined in SearchTerm[1].LogicalConnector, SearchTerm[2].LogicalConnector, SearchTerm[3].LogicalConnector.
While putting brackets is easy, choosing how to "describe" them is complex, unless you change significantly your SearchTerm collection (it couldn't be a "linear" array but it would need to be a tree).
P.S. I was wrong, you don't need an ExpressionVisitor. You need an ExpressionVisitor when you are trying to "merge" multiple LambdaExpressions that have distinct ParameterExpression. In this code we are able to have a single var obj = ExpressionHelper.Parameter<TEntity>() for all the query, so no problems merging the conditions. To make it clear: if you want to "merge" x1 => x1.Foo == "Foo1" with x2 => x2.Foo == "Foo2" then you need an ExpressionVisitor that replaces x2 with x1, otherwise you would get a wrong query like x1 => x1.Foo == "Foo1" || x2.Foo == "Foo2". In the code given we have only x1 (that is var obj = ExpressionHelper.Parameter<TEntity>()), so no problem.
I'm trying to build the following lambda expression using the expression tree ->
info => info.event_objects.Select(x => x.object_info.contact_info)
I researched a lot and find some answers on the StackOverflow.
This one helped me to build the
info =>
info.event_objects.Any(x => x.object_info.contact_info.someBool == true)
As you can see, the method 'Any' is easy to get.
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any"
&& m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
The main problem is with the method 'Select'. If you will try to change the Name "Any" to "Select", you will get the following exception:
var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name ==
"Select" && m.GetParameters().Length == 2);
selectMethod = selectMethod.MakeGenericMethod(childType);
Additional information: Sequence contains more than one matching element
Another way I've tried:
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name
== "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p =>
p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
It seems work, but then I get the exception here:
navigationPropertyPredicate = Expression.Call(selectMethod, parameter,
navigationPropertyPredicate);
Additional information: Method
System.Collections.Generic.IEnumerable`1[TResult] Select[TSource,TResult]
(System.Collections.Generic.IEnumerable`1[TSource],
System.Func`2[TSource,TResult]) is a generic method definition>
After that, I've tried to use:
selectMethod = selectMethod.MakeGenericMethod(typeof(event_objects),
typeof(contact_info));
In fact, it doesn't help.
Here is my full code
public static Expression GetNavigationPropertyExpression(Expression parameter, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, "x");
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, innerProperties);
if (isCollection)
{
//var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Select" && m.GetParameters().Length == 2);
//selectMethod = selectMethod.MakeGenericMethod(childType);
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
navigationPropertyPredicate = Expression.Call(selectMethod, parameter, navigationPropertyPredicate);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(true, typeof(bool));
navigationPropertyPredicate = Expression.Lambda(left);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
[TestMethod]
public void TestDynamicExpression()
{
var parameter = Expression.Parameter(typeof(event_info), "x");
var expression = GetNavigationPropertyExpression(parameter, "event_objects", "object_info", "contact_info");
}
Edit: unfortunately, I've tried answers from this question, but it doesn't seem work
You can avoid finding the correct generic method overload via reflection (which is complicated and error prone as you already noticed) by using one of the two Expression.Call method overloads (one for static and one for instance methods) accepting string methodName and Type[] typeArguments.
Also the current implementation is overcomplicated and contains other problems, due to the lack of clear separation of expression and lambda expression building.
Here is a correct working implementation:
public static LambdaExpression GetNavigationPropertySelector(Type type, params string[] properties)
{
return GetNavigationPropertySelector(type, properties, 0);
}
private static LambdaExpression GetNavigationPropertySelector(Type type, string[] properties, int depth)
{
var parameter = Expression.Parameter(type, depth == 0 ? "x" : "x" + depth);
var body = GetNavigationPropertyExpression(parameter, properties, depth);
return Expression.Lambda(body, parameter);
}
private static Expression GetNavigationPropertyExpression(Expression source, string[] properties, int depth)
{
if (depth >= properties.Length)
return source;
var property = Expression.Property(source, properties[depth]);
if (typeof(IEnumerable).IsAssignableFrom(property.Type))
{
var elementType = property.Type.GetGenericArguments()[0];
var elementSelector = GetNavigationPropertySelector(elementType, properties, depth + 1);
return Expression.Call(
typeof(Enumerable), "Select", new Type[] { elementType, elementSelector.Body.Type },
property, elementSelector);
}
else
{
return GetNavigationPropertyExpression(property, properties, depth + 1);
}
}
The first is the public method. It internally uses the next two private methods to recursively build the desired lambda. As you can see, I distinguish between building lambda expression and just expression to be used as lambda body.
Test:
var selector = GetNavigationPropertySelector(typeof(event_info),
"event_objects", "object_info", "contact_info");
Result:
x => x.event_objects.Select(x1 => x1.object_info.contact_info)
"Additional information: Sequence contains more than one matching element"
Unlike "Any()", for "Select()" there are two overloads with two parameters:
Select<TS, TR>(IE<TS> source, Func<TS, TR> selector)
Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector)
(takes the "(item, index) => " selector lambda)
Since your code already relies on "esoteric knowledge" anyway, just take the first one of them:
var selectMethod = typeof(Enumerable).GetMethods()
.First(m => m.Name == nameof(Enumerable.Select)
&& m.GetParameters().Length == 2);
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
I'm using the Entity Framework and I developed this extension method:
public static IQueryable<TResult> Like<TResult>(this IQueryable<TResult> query, Expression<Func<TResult, string>> field, string value)
{
var expression = Expression.Lambda<Func<TResult, bool>>(
Expression.Call(field.Body, typeof(string).GetMethod("Contains"),
Expression.Constant(value)), field.Parameters);
return query.Where(expression);
}
this code work correctly if I use it like this:
var result = from e in context.es.Like(r => r.Field, "xxx")
select e
Now I need to call this extension method programmatically:
public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
// Collect fields
PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
List<string> fields = new List<string>();
foreach (PropertyInfo propertyInfo in propertiesInfo)
{
if (
(propertyInfo.PropertyType == typeof(string)) ||
(propertyInfo.PropertyType == typeof(int)) ||
(propertyInfo.PropertyType == typeof(long)) ||
(propertyInfo.PropertyType == typeof(byte)) ||
(propertyInfo.PropertyType == typeof(short))
)
{
fields.Add(propertyInfo.Name);
}
}
ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
Expression expression = Expression.Lambda(Expression.Property(parameter, typeof(TSource).GetProperty(fields[0])), parameter);
Expression<Func<TSource, string>> field = Expression.Lambda<Func<TSource, string>>(expression, parameter);
return source.Like(field, textToFind);
}
Now this code doesn't work!
I need to understand how to declare the "field" of the Like extended methods.
Expression<Func<TSource, string>> field = Expression.Lambda<Func<TSource, string>>(expression, parameter);
At runtime I receive this error: Impossibile utilizzare un'espressione di tipo 'System.Func`2[TestMdf.Equipment,System.String]' per un tipo restituito 'System.String'
I assume your second code snippet is just a truncated example - if you really did that, then the results would be unpredictable, because you're taking the first property returned by reflection, which can change between runs of your program.
You'll get better answers if you say "This worked" followed by a description of what happened, and "This didn't work" followed by a description of how you could tell that it didn't work (compiler error? runtime error? exception message?)
Firstly, are you aware of Dynamic Linq? It allows you to delay decisions about how a query should be structured until runtime and may solve many of your problems for you.
But assuming this is a learning exercise...
Your Like extension method takes an expression (which a caller ought to usually write out as a lambda, as that's the whole point of these things). That expression will convert a "record" from a query result set and return a string value (presumably selecting it from the data stored in the record). The method also takes a value string.
But it then constructs (by hand) its own predicate that calls the Contains method on the body of the field lambda.
I guess this ought to work, because the result of that lambda is a string. However, I can't see why you're doing this the hard way. What's wrong with:
var result = from e in context.es
where e.Field.Contains("xxx"))
select e
Now I found a partial solution to my problem:
public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
// Collect fields
PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
List<string> fields = new List<string>();
foreach (PropertyInfo propertyInfo in propertiesInfo)
{
if (
(propertyInfo.PropertyType == typeof(string)) ||
(propertyInfo.PropertyType == typeof(int)) ||
(propertyInfo.PropertyType == typeof(long)) ||
(propertyInfo.PropertyType == typeof(byte)) ||
(propertyInfo.PropertyType == typeof(short))
)
{
fields.Add(propertyInfo.Name);
}
}
ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
var property = typeof(TSource).GetProperty(fields[0]);
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var constantValue = Expression.Constant(textToFind);
var equality = Expression.Call(Expression.Call(Expression.Property(parameter, property), "ToUpper", null, null), typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), Expression.Constant(textToFind.ToUpper()));
return source.Where(Expression.Lambda<Func<TSource, bool>>(equality, parameter));
}
Now the next step is to concatenate all the field list:
" " + fields[0] + " " + ... fields[n]
Some ideas?
This is my first release:
public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
if (textToFind.Trim() == "")
{
return source;
}
string[] textToFindList = textToFind.Replace("'", "''").Split(' ');
// Collect fields
PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
List<string> fieldList = new List<string>();
foreach (PropertyInfo propertyInfo in propertiesInfo)
{
if (
(propertyInfo.PropertyType == typeof(string)) ||
(propertyInfo.PropertyType == typeof(int)) ||
(propertyInfo.PropertyType == typeof(long)) ||
(propertyInfo.PropertyType == typeof(byte)) ||
(propertyInfo.PropertyType == typeof(short))
)
{
fieldList.Add(propertyInfo.Name);
}
}
ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
MethodInfo concatMethod = typeof(String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) });
var spaceExpression = Expression.Constant(" ");
var concatenatedField = BinaryExpression.Add(spaceExpression, Expression.MakeMemberAccess(parameter, typeof(TSource).GetProperty(fieldList[0])), concatMethod);
for (int i = 1; i < fieldList.Count; i++)
{
concatenatedField = BinaryExpression.Add(concatenatedField, spaceExpression, concatMethod);
concatenatedField = BinaryExpression.Add(concatenatedField, Expression.MakeMemberAccess(parameter, typeof(TSource).GetProperty(fieldList[i])), concatMethod);
}
concatenatedField = BinaryExpression.Add(concatenatedField, spaceExpression, concatMethod);
var fieldsExpression = Expression.Call(concatenatedField, "ToUpper", null, null);
var clauseExpression = Expression.Call(
fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
Expression.Constant(textToFindList[0].ToUpper())
);
if (textToFindList.Length == 1)
{
return source.Where(Expression.Lambda<Func<TSource, bool>>(clauseExpression, parameter));
}
BinaryExpression expression = Expression.And(Expression.Call(
fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
Expression.Constant(textToFindList[1].ToUpper())
), clauseExpression);
for (int i = 2; i < textToFindList.Length; i++)
{
expression = Expression.And(Expression.Call(
fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
Expression.Constant(textToFindList[i].ToUpper())
), expression);
}
return source.Where(Expression.Lambda<Func<TSource, bool>>(expression, parameter));
}
I will modify to manage some rules like "phrase" + and - operator.