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;
}
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 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)
I am creating a delegate for a Select statement in LINQ. Some of the property bindings are to child properties on the object I'm selecting from.
This is the LINQ statement I want to put in my delegate:
var list = dataSet.Select(x => new ViewModel()
{
Name = x.Name,
ClassType = x.ClassType.Description
};
I can get the Name no worries with my code, but I do not know how to get the ClassType.Description.
Here is my current code:
protected Func<Student, ManagerStudentListViewModel> GetSelectStatement()
{
var studentType = typeof(Student);
var viewModelType = typeof(ManagerStudentListViewModel);
var parameterExpression = Expression.Parameter(studentType, "x");
var newInstantiationExpression = Expression.New(viewModelType);
// Name Binding
var viewModelProperty = viewModelType.GetProperty("Name");
var studentProperty = studentType.GetProperty("Name");
var nameMemberExpression = Expression.Property(parameterExpression, studentProperty);
var nameBinding = Expression.Bind(viewModelProperty, nameMemberExpression);
// ClassType.Description Binding
// ???
var bindings = new List<MemberAssignment>() { nameBinding, classTypeBinding };
var memberInitExpression = Expression.MemberInit(newInstantiationExpression, bindings);
var lambda = Expression.Lambda<Func<Student, ManagerStudentListViewModel>>(memberInitExpression, parameterExpression);
return lambda.Compile();
}
Accessing deeply nested members is no different than accessing any other properties, provided you know the name of the members. Just create an expression to get the first property, then add the expression to get the second.
Expression<Func<Student, ManagerStudentListViewModel>> GetSelectStatement()
{
var studentType = typeof(Student);
var viewModelType = typeof(ManagerStudentListViewModel);
var param = Expression.Parameter(studentType, "x");
var nameValue = Expression.Property(param, "Name");
var classTypeValue = Expression.Property(
Expression.Property(param, "ClassType"), // get the class type
"Description"); // get the description of the class type
var nameMemberBinding = Expression.Bind(
viewModelType.GetProperty("Name"),
nameValue);
var classTypeMemberBinding = Expression.Bind(
viewModelType.GetProperty("ClassType"),
classTypeValue);
var initializer = Expression.MemberInit(
Expression.New(viewModelType),
nameMemberBinding,
classTypeMemberBinding);
return Expression.Lambda<Func<Student, ManagerStudentListViewModel>>(initializer, param);
}
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));
My LINQ query is like the below code.
var data2 = data.Where(c => String.Format("{0:MM/dd/yyyy}", c.OrderDate) == "07/04/1996");
I need to customize the predicate for formatted column in the below expression. I need to write the Expression for predicate and filter the data based on format. please check below code.
pred =Expression.Equal(membertype, Expression.Constant(value, type));
lambda = Expression.Lambda(predicate, paramExpression);
source.Where(paramExpression, predicate);
Thanks.
Here's a sample that creates the .Where dynamically.
static void DynamicWhereBuilder() {
var datas = new Data[] {
new Data { OrderDate = "07/04/1996"},
new Data { OrderDate = "07/04/1990"},
new Data { OrderDate = "07/04/2001"},
new Data { OrderDate = "2012/04/07"}
};
IQueryable<Data> queryableData = datas.AsQueryable<Data>();
var formatConstant = Expression.Constant("{0:MM/dd/yyyy}", typeof(string));
var parameter = Expression.Parameter(typeof(Data), "dataArg");
var property = Expression.Property(parameter, "OrderDate");
var left = Expression.Call(property, typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }), formatConstant, property);
var right = Expression.Constant("07/04/2001", typeof(string));
var equal = Expression.Equal(left, right);
var whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<Data, bool>>(equal, new ParameterExpression[] { parameter }));
var results = queryableData.Provider.CreateQuery<Data>(whereCallExpression); // returns the object with OrderDate = "07/04/2001"
}