I have create where clause for equal successfully using dynamic linq expression using following code-
Expression<Func<T, bool>> whereClause = null;
var parameter = Expression.Parameter(typeof(T), "c");
Expression filterExpression = null;
List<Filter> filters = new List<Filter>(){new Filter(){PropertyName= "id" , Value="2" , Operation = "Equal"}}
var DistinctItems = filters.GroupBy(x => x.PropertyName).Select(y => y.First());
foreach (var filterItem in DistinctItems)
{
var member = Expression.Property(parameter, filterItem.PropertyName);
var propertyType = ((PropertyInfo)member.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(propertyType);
if (!converter.CanConvertFrom(typeof(string)))
throw new NotSupportedException();
var propertyValue = converter.ConvertFromInvariantString(filterItem.Value.ToString());
var constant = Expression.Constant(propertyValue);
var valueExpression = Expression.Convert(constant, propertyType);
Expression equality = null;
switch (filterItem.Operation)
{
case "Equal":
equality = Expression.Equal(member, valueExpression);
break;
case "LessThan":
equality = Expression.LessThan(member, valueExpression);
break;
//case "In": how to do for IN forexample where id in
//(1,2)
// equality = Expression.(member, valueExpression);
// break;
}
if (filterExpression == null)
{
filterExpression = equality;
}
else
{
filterExpression = Expression.And(filterExpression, equality);
}
}
if (filterExpression != null)
{
whereClause = Expression.Lambda<Func<T, bool>>(filterExpression, parameter);
}
return whereClause;
How to do it for IN clause , for example - Where Id IN (1,2)
Related
I'm using Visual Studio 2019. I'm doing filter with the below function. My search result is always sensitive for a string value. For employee name, it would be case-insensitive search, so user can enter upper or lowercase searchValue. My list is converted ToList() to AsQueryable()
list = list.Where(AppendFilters<CustomerApiResponse>(data.filter.filters));
Please see the below function:
protected ExpressionStarter<T> AppendFilters<T>(List<Filter2> filters)
{
var predicate = PredicateBuilder.New<T>();
foreach (var item in filters)
{
predicate = predicate.And(GetExpression<T>(item.field, item.value, item.#operator));
}
return predicate;
}
Here's what I have:
private Expression<Func<T, bool>> GetExpression<T>(string propName, object val, string op)
{
ParameterExpression argParam = Expression.Parameter(typeof(T));
var nameProperty = Expression.Property(argParam, propName);
var opp = EnumExtensions.ParseEnum<KendoOperators>(op).GetDescription();
MethodInfo method = typeof(string).GetMethod(opp, new[] { typeof(string) });
Expression e1;
if (nameProperty.Type == typeof(int))
{
var propertyType = ((PropertyInfo)nameProperty.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(propertyType);
if (!converter.CanConvertFrom(typeof(string)))
throw new NotSupportedException();
var propertyValue = converter.ConvertFrom(val);
var constant = Expression.Constant(propertyValue);
var valueExpression = Expression.Convert(constant, propertyType);
if (op == "neq")
{
e1 = Expression.NotEqual(nameProperty, valueExpression);
}
else
{
e1 = Expression.Equal(nameProperty, valueExpression);
}
}
else
{
var constant = Expression.Constant(val);
e1 = Expression.Call(nameProperty, method, constant);
}
return Expression.Lambda<Func<T, bool>>(e1, argParam);
}
How do I change the above code to make filter to be case-insensitive for searches any string value?
Thank you!
I am creating lambda expression for Iqueryable to get value from a collection but I want to convert that value to other datatype like int or decimal. So as I cannot use c# casting with Iqueryable so I have created user defined scalar function in sql and trying to access that in expression but it throws exception that the 'methodname' cannot be converted to sql expression.
public class Context
{
[DbFunction("dbo", "ConvertToDouble")]
public int? ConvertToDouble(string value)
{
var sql = $"set #result = dbo.[ConvertToDouble]('{value}')";
var output = new SqlParameter { ParameterName = #"result", DbType = DbType.Int32, Size = 16, Direction = ParameterDirection.Output };
var result = Database.ExecuteSqlCommand(sql, output);
return output.Value as int?;
}
}
private static Expression<Func<TSource, TDataType>> CreateLamdaExpression<TSource, TDataType>(string fieldName)
{
var parameterExpression = Expression.Parameter(typeof(TSource));
var collectionParameter = Expression.Property(parameterExpression, "CustomFieldValues");
var childType = collectionParameter.Type.GetGenericArguments()[0];
var propertyParameter = Expression.Parameter(childType, childType.Name);
var left = Expression.Property(propertyParameter, "Name");
var right = Expression.Constant(fieldName);
var innerLambda = Expression.Equal(left, right);
var innerFunction = Expression.Lambda(innerLambda, propertyParameter);
var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "FirstOrDefault" && m.GetParameters().Length == 2).FirstOrDefault().MakeGenericMethod(typeof(CustomFieldValue));
var outerLambda = Expression.Call(method, Expression.Property(parameterExpression, collectionParameter.Member as System.Reflection.PropertyInfo), innerFunction);
var propertyGetter = Expression.Property(outerLambda, "Value");
if (typeof(TDataType) != typeof(object))
{
/var changeTypeCall = Expression.Call(Expression.Constant(Context), Context.GetType().GetMethod("ConvertToDouble", BindingFlags.Public | BindingFlags.Instance),
propertyGetter
);
Expression convert = Expression.Convert(changeTypeCall,
typeof(TDataType));
return Expression.Lambda<Func<TSource, TDataType>>(convert, new ParameterExpression[] { parameterExpression });
}
var result = Expression.Lambda<Func<TSource, TDataType>>(propertyGetter, new ParameterExpression[] { parameterExpression });
return result;
}
Recently Im trying to upgrade My MVC project to dotnetCore project, while something went wrong in BaseService..
my former MVC code which works good:
public List<TEntity> FindList(Expression<Func<TEntity, bool>> predicate, Pagination pagination)
{
using (var ent = new Entities())// System.Data.Entity.DbContext
{
bool isAsc = pagination.sord.ToLower() == "asc";
string[] order = pagination.sidx.Split(',');
MethodCallExpression resultExp = null;
var tempData = ent.Set<TEntity>().Where(predicate);
foreach (string item in order)
{
string orderPart = item;
orderPart = Regex.Replace(orderPart, #"\s+", " ");
string[] orderArry = orderPart.Split(' ');
string orderField = orderArry[0];
if (orderArry.Length == 2)
{
isAsc = orderArry[1].ToUpper() == "ASC";
}
var parameter = Expression.Parameter(typeof(TEntity), "t");
var property = typeof(TEntity).GetProperty(orderField);
if (property != null)
{
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending", new Type[] { typeof(TEntity), property.PropertyType }, tempData.Expression, Expression.Quote(orderByExp));
}
}
if (resultExp != null) tempData = tempData.Provider.CreateQuery<TEntity>(resultExp);
pagination.records = tempData.Count();
tempData = tempData.Skip<TEntity>(pagination.rows * (pagination.page - 1)).Take<TEntity>(pagination.rows).AsQueryable();
return tempData.ToList();
}
}
and the dotnetCore code:
public async Task<List<TEntity>> FindList(Expression<Func<TEntity, bool>> predicate, Pagination pagination)
{
bool isAsc = pagination.sord.ToLower() == "asc";
string[] order = pagination.sidx.Split(',');
MethodCallExpression resultExp = null;
var tempData = DbSet.Where(predicate);//a instance of Microsoft.EntityFrameworkCore.DbSet<TEntity>
foreach (string item in order)
{
string orderPart = item;
orderPart = Regex.Replace(orderPart, #"\s+", " ");
string[] orderArry = orderPart.Split(' ');
string orderField = orderArry[0];
if (orderArry.Length == 2)
{
isAsc = orderArry[1].ToUpper() == "ASC";
}
var parameter = Expression.Parameter(typeof(TEntity), "t");
var property = typeof(TEntity).GetProperty(orderField);
if (property != null)
{
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending", new Type[] { typeof(TEntity), property.PropertyType }, tempData.Expression, Expression.Quote(orderByExp));
}
}
if (resultExp != null) tempData = tempData.Provider.CreateQuery<TEntity>(resultExp);
pagination.records = tempData.Count();
var list = await tempData.Skip<TEntity>(pagination.rows * (pagination.page - 1)).Take<TEntity>(pagination.rows).ToListAsync();
return list;
}
and what strange is the code works well in just ordersorting or in just pagination;
but in both conditions(pagination&sorting) , the Second pagedata always went wrong(first pagedata always right);
page1:
page2:
I dont know why but I replace the last 2 line code, the data comes right...
anyone can announce the mechanism?
var list = await tempData.Skip<TEntity>(pagination.rows * (pagination.page - 1)).Take<TEntity>(pagination.rows).ToListAsync();
return list;
as
var list = tempData.ToAsyncEnumerable().Skip<TEntity>(pagination.rows * (pagination.page - 1)).Take<TEntity>(pagination.rows);
return await list.ToList();
I am new to linq c# , Following is my function
public static IQueryable<T> BuildWhereExpression<T>(this IQueryable<T> query, SearchAttributes searchModel)
{
string FilterField = searchModel.FilterField;
string FilterOperator = searchModel.FilterOperator;
string FilterValue = searchModel.FilterValue;
ParameterExpression ParamExp = Expression.Parameter(typeof(T), GlobalConstants.SearchExpressionName);
Expression InitialExp;
LambdaExpression FinalExp;
switch (FilterOperator)
{
case GlobalConstants.IsEqualTo:
if (FilterValue == "")
InitialExp = Expression.Call(Expression.PropertyOrField(ParamExp, FilterField), typeof(string).GetMethod("Contains"), Expression.Constant(FilterValue));
else
InitialExp = Expression.Equal(Expression.PropertyOrField(ParamExp, FilterField), Expression.Constant(FilterValue));
break;
case GlobalConstants.Contains:
{ // This is what i havd tried till now
//var Column = Expression.PropertyOrField(ParamExp, FilterField);
//var isNull = Expression.Equal(Column, Expression.Constant(null));
//Expression left = Expression.Call(Column, typeof(string).GetMethod("ToString", System.Type.EmptyTypes));
//Expression left = Expression.Call(pe)
}
InitialExp = Expression.Call(Expression.PropertyOrField(ParamExp, FilterField), typeof(string).GetMethod("Contains"), Expression.Constant(FilterValue));
break;
case GlobalConstants.StartsWith:
InitialExp = Expression.Call(Expression.PropertyOrField(ParamExp, FilterField), typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), Expression.Constant(FilterValue));
break;
default:
InitialExp = Expression.Constant(true);
break;
}
FinalExp = Expression.Lambda<Func<T, bool>>(InitialExp, new ParameterExpression[] { ParamExp });
MethodCallExpression result = Expression.Call(typeof(Queryable), "Where", new Type[] { query.ElementType }, query.Expression, Expression.Quote(FinalExp));
return query.Provider.CreateQuery<T>(result);
}
The above code adds a condition for contains in a column dynamically.
Contains does not works for column containing null values.
How can i implement following logic
If table.ColumnValue is Null replace the column null with empty string then compair with the value in FilterValue
EDIT:
I mean how can i implement query as
coalesce(table.column,string.empty) == FilterValue
Please help me over this.
Thanks in advance.
The expression you are looking for is something like:
Expression<Func<T, bool>> exp = x => (x.FilterField ?? string.Empty).Contains(FilterValue);
that can be obtained with
var coalesce = Expression.Coalesce(
Expression.PropertyOrField(ParamExp, FilterField),
Expression.Constant(string.Empty))
so
InitialExp = Expression.Call(coalesce, typeof(string).GetMethod("Contains"), Expression.Constant(FilterValue));
Note that, considering future-proofing, I would always explicitly tell the .NET the parameters of the method I'm looking for:
typeof(string).GetMethod("Contains", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);
because you can't know if, in .NET ∞.0, they'll finally add an overload that supports a StringComparison :-)
I have the following code block I am using to perform some dynamic filtering on a generic IQueryable list
private static MethodInfo miTL = typeof(String).GetMethod("ToLower", Type.EmptyTypes);
public static IQueryable<T> Where<T>(IQueryable<T> source, string member, object value)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (member == null)
{
throw new ArgumentNullException("member");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value is string && !string.IsNullOrWhiteSpace(value.ToString()))
{
//If the type is string, force lowercase equals comparison for both sides.
value = value.ToString().ToLower();
var parameter = Expression.Parameter(typeof(T), "item");
var parameterProperty = Expression.Property(parameter, member);
//ToLower dynamic expression
var dynamicExpression = Expression.Call(parameterProperty, miTL);
var constantValue = Expression.Constant(value);
var equalsExpression = Expression.Equal(dynamicExpression, constantValue);
var lambdaExpression = Expression.Lambda<Func<T, bool>>(equalsExpression, parameter);
return source.Where(lambdaExpression);
}
else
{
var item = Expression.Parameter(typeof(T), "item");
var prop = Expression.Property(item, member);
var soap = Expression.Constant(value);
var equal = Expression.Equal(prop, soap);
var lambda = Expression.Lambda<Func<T, bool>>(equal, item);
return source.Where(lambda);
}
}
This all works fine - apart from the possibility of my source containing null values, which then returns a null reference exception.
It directly translates to (when the field is "Counterparty" :- {item => (item.Counterparty.ToLower() == "name of counterparty")}
What I actually need in Lambda expression form is :-
{item => !string.IsNullEmptyOrWhitespace(item.Counterparty) && (item.Counterparty.ToLower() == "name of counterparty")}
Any ideas how to achieve this dynamically?
--REVIEWED--
Here is the whole replacement code, using a much nicer string.Equals check
public static IQueryable<T> Where<T>(IQueryable<T> source, string member, object value)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (member == null)
{
throw new ArgumentNullException("member");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value is string && !string.IsNullOrWhiteSpace(value.ToString()))
{
var parameter = Expression.Parameter(typeof(T), "item");
var parameterProperty = Expression.Property(parameter, member);
var body =
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrEmpty", null, parameterProperty)
),
Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant(value),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase))
);
var body2 = Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant(value),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase));
var lambdaExpression = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(lambdaExpression);
}
else
{
var item = Expression.Parameter(typeof(T), "item");
var prop = Expression.Property(item, member);
var soap = Expression.Constant(value);
var equal = Expression.Equal(prop, soap);
var lambda = Expression.Lambda<Func<T, bool>>(equal, item);
return source.Where(lambda);
}
}
A literal translation would be something like:
var body =
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrWhiteSpace", null,
parameterProperty)
),
Expression.Equal(
Expression.Call(parameterProperty, "ToLower", null),
Expression.Constant("name of counterparty")
)
);
However, you would do well to look at the various string.Equals overloads. For example:
var body = Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant("name of counterparty"),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase));