I have extended a custom search for IQueryable using Expression trees and it works fine. There are two set of search results in this custom search that gets merged together at the very last line using the Concat method. I would like to retain the order by showing firstRankResult first and secondRankResult second. The current code almost works until it gets sent to a asp:gridview with asp:linqdatasource with pagination and for some reason beyond my understanding LinqToSql throws an OrderBy in the beginning messing the order up. The end end result query then becomes something like:
SELECT Row_Number() Over( Order By ...all fields...) ...
This messes the order of results and the first set no longer shows on the top of the list.
Long story short, I thought of adding a SearchRank field to each resultset and at the end force it to order by SearchRank. So if each IQueryable produces this SQL: SELECT F1,F2 FROM T1 could I make it be SELECT 1 AS [SearchRank],F1,F2 FROM T1 and similar for the next IQueryable. Then I concat them together with order by SearchRank. Any idea if this can be done using dynamic expression trees?
public static IQueryable<T> RankedSearch<T>(this IQueryable<T> source, string[] fieldNames, string[] searchKeywords)
{
if (source == null)
throw new ArgumentNullException();
//Building Expression Tree
var string0Expression = Expression.Constant("0", typeof(string));
var string1Expression = Expression.Constant("1", typeof(string));
var alwaysTrueExpression = Expression.Equal(string0Expression, string0Expression);
var alwaysFalseExpression = Expression.Equal(string0Expression, string1Expression);
Expression firstRankExpression = alwaysTrueExpression;
Expression secondRankExpression = alwaysFalseExpression;
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
ParameterExpression pe = Expression.Parameter(typeof(T), "type");
foreach (var keyword in searchKeywords)
{
Expression cumContainsExpression = alwaysFalseExpression;
Expression valueExpression = Expression.Constant(keyword, typeof(string));
foreach (var fieldName in fieldNames)
{
Expression propertyExpression;
var fieldNameTree = fieldName.Split('.');
if (fieldNameTree.Length > 1)
{
var fieldParent = Expression.Property(pe, fieldNameTree[0]);
propertyExpression = Expression.Property(fieldParent, fieldNameTree[1]);
}
else
{
propertyExpression = Expression.Property(pe, fieldName);
}
Expression containsExpression = Expression.Call(propertyExpression, containsMethod, valueExpression);
cumContainsExpression = Expression.OrElse(cumContainsExpression, containsExpression);
}
firstRankExpression = Expression.AndAlso(firstRankExpression, cumContainsExpression);
secondRankExpression = Expression.OrElse(secondRankExpression, cumContainsExpression);
}
MethodCallExpression whereCallExpressionFirstRank = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(firstRankExpression, new ParameterExpression[] { pe }));
IQueryable<T> firstRankResult = source.Provider.CreateQuery<T>(whereCallExpressionFirstRank);
//This becomes SELECT F1,F2 FROM T1 WHERE (F1 LIKE '%keyword%' AND F2 LIKE '%keyword%')
//DESIRED OUTPUT SELECT 1 AS [SearchRank], F1,F2 FROM T1 WHERE (F1 LIKE '%keyword%' AND F2 LIKE '%keyword%')
MethodCallExpression whereCallExpressionSecondRank = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(secondRankExpression, new ParameterExpression[] { pe }));
IQueryable<T> secondRankResult = source.Provider.CreateQuery<T>(whereCallExpressionSecondRank);
//This becomes SELECT F1,F2 FROM T1 WHERE (F1 LIKE '%keyword%' OR F2 LIKE '%keyword%')
//DESIRED OUTPUT SELECT 2 AS [SearchRank], F1,F2 FROM T1 WHERE (F1 LIKE '%keyword%' OR F2 LIKE '%keyword%')
return firstRankResult.Concat(secondRankResult.Except(firstRankResult));
}
I figured it out.
var firstRankResult1 = firstRankResult.Select(r => new
{
baseType = r,
RankId = 1
});
var secondRankResult1 = secondRankResult.Except(firstRankResult).Select(r => new
{
baseType = r,
RankId = 2
});
var unionedResult = firstRankResult1.Concat(secondRankResult1).OrderBy(o => o.RankId).Select(o => o.baseType);
return unionedResult;
Related
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;
}
How to create "inline if statement" with expressions, in dynamic select, for null checking ?
I wrote a dynamic select LINQ expression, for a nested property of an object, but it throws an exception when it is null. so i want to check whether that property is null or not, that simple !
here is what i mean:
X.Where(...)
.Select(X => new Y{
...
Z = X.Titles == null ? "" : [Linq]
...
}).FirstOrDefault();
here is what i wrote
private static Expression GetLocalizedString(Expression stringExpression, SupportedCulture supportedCulture)
{
var expression = Expression.Parameter(typeof(APILocalizedString), nameof(APILocalizedString));
var prop = Expression.Property(expression, nameof(APILocalizedString.SupportedCulture));
var value = Expression.Constant(supportedCulture);
var condition = Expression.Equal(prop, value);
var where = Expression.Call(
typeof (Enumerable),
nameof(Enumerable.Where),
new Type[] { typeof(APILocalizedString) },
stringExpression,
Expression.Lambda<Func<APILocalizedString, bool>>(condition, expression));
var select = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Select),
new Type[] { typeof(APILocalizedString), typeof(string) },
where,
Expression.Lambda<Func<APILocalizedString, string>>(
Expression.Property(expression, nameof(APILocalizedString.Text)),
expression
));
var first = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.First),
new Type[] { typeof(APILocalizedString) },
stringExpression);
var defaultIfEmpty = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.DefaultIfEmpty),
new Type[] { typeof(string) },
select,
first);
var firstOrDefault =
Expression.Call(
typeof(Enumerable),
nameof(Enumerable.FirstOrDefault),
new Type[] { typeof(string) },
defaultIfEmpty);
var nullCheck = Expression.Equal(stringExpression, Expression.Constant(null, stringExpression.Type));
var result = Expression.IfThenElse(nullCheck, Expression.Constant(""), firstOrDefault);
return result;
}
here is what GetLocalizedString generated:
{IIF((X.Titles == null), "", X.Titles.Where(APILocalizedString => (APILocalizedString.SupportedCulture == EN)).DefaultIfEmpty(X.Titles.First()).Select(APILocalizedString => APILocalizedString.Text).FirstOrDefault())}
Select expression
... bindings.Add(Expression.Bind(property, GetLocalizedString(Expression.Property(parameter, "Titles"), SupportedCulture.EN))); ...
and here is the error message:
System.ArgumentException: 'Argument types do not match'
Select property is of type String
is there any way to create an expresion like X.Titles == null ? "" : [Linq] ?
The expression equivalent of the C# conditional ?: operator is Expression.Condition. While Expression.IfThenElse you are using is the equivalent of C# if then else block.
Both methods return ConditionalExpression with Test, IfTrue and IfFalse properties populated. The difference is that the result Type of the Condition is the type of the operands, while for IfThenElse it is void, hence cannot be used in query expression trees.
So the answer to your concrete question is:
var result = Expression.Condition(nullCheck, Expression.Constant(""), firstOrDefault);
P.S. As a side node, I'm getting several errors from your code snippet, so I had to rearrange it like this in order to get w/o error to the above line:
private static Expression GetLocalizedString(Expression stringExpression, SupportedCulture supportedCulture)
{
var expression = Expression.Parameter(typeof(APILocalizedString), nameof(APILocalizedString));
var prop = Expression.Property(expression, nameof(APILocalizedString.SupportedCulture));
var value = Expression.Constant(supportedCulture);
var condition = Expression.Equal(prop, value);
var where = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Where),
new Type[] { typeof(APILocalizedString) },
stringExpression,
Expression.Lambda<Func<APILocalizedString, bool>>(condition, expression));
var first = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.First),
new Type[] { typeof(APILocalizedString) },
stringExpression);
var defaultIfEmpty = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.DefaultIfEmpty),
new Type[] { typeof(APILocalizedString) },
where,
first);
var select = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Select),
new Type[] { typeof(APILocalizedString), typeof(string) },
defaultIfEmpty,
Expression.Lambda<Func<APILocalizedString, string>>(
Expression.Property(expression, nameof(APILocalizedString.Text)),
expression
));
var firstOrDefault =
Expression.Call(
typeof(Enumerable),
nameof(Enumerable.FirstOrDefault),
new Type[] { typeof(string) },
select);
var nullCheck = Expression.Equal(stringExpression, Expression.Constant(null, stringExpression.Type));
var result = Expression.Condition(nullCheck, Expression.Constant(""), firstOrDefault);
return result;
}
I have a situation where I am using a linq provider that does not support the .Contains method to generate a WHERE IN clause in the query. I am looking for a way to generate the (Value = X OR Value = Y OR Value = Z) statement dynamically from the list of items to match. I haven't found a good example of building expression trees to do this.
Normally I would query this way:
var names = new string[] { "name1", "name2", "name3" }
var matches = query.Where(x => names.Contains(x.Name));
So far the closest thing I could find was to use the
Dynamic Linq Library and build a string to be interpreted but it feels a bit too hacky.
You don't even need an external library for something like this:
var names = new string[] { "name1", "name2", "name3" };
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
Expression expression = null;
foreach (string name in names)
{
Expression expression2 = Expression.Equal(prop, Expression.Constant(name));
if (expression == null)
{
expression = expression2;
}
else
{
expression = Expression.OrElse(expression, expression2);
}
}
var query = ...; // Your query
if (expression != null)
{
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}
You can build a concatenation of Expression.OrElse, with the comparisons between the property Name and one of the strings.
In this particular case (3 strings), the resulting Expression, when looked from the debugger, is:
(((Param_0.Name == "name1") OrElse (Param_0.Name == "name2")) OrElse (Param_0.Name == "name3"))
Just improving the answer by xanatos:
var names = new string[] { "name1", "name2", "name3" };
var query = ...; // Your query
if (names.Any())
{
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
var expression=names
.Select(v => Expression.Equal(prop, Expression.Constant(v)))
.Aggregate(Expression.OrElse);
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}
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 :-)
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"
}