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"
}
Related
I am trying to create an Expression Tree for Enumerable.Any(IEnumerable, Func<T,bool>)
Actually, I have a list and need to check whether this list has at least 1 element that contains "test"
So it looks very simple:
List<string> strings = new List<string> { "element1", "element2", "element3" };
var test = strings.Any(x => x.Contains("test"));
I am trying to create an Expression Tree and GetMethod returns null, so I think I miss something
Here is a test code:
List<string> strings = new List<string> { "element1", "element2", "element3" };
var testString = "test";
ParameterExpression p = Expression.Parameter(typeof(string), "item");
Delegate predicate = Expression.Lambda(
Expression.Call(
p,
typeof(string).GetMethod("Contains", new[] { typeof(string) }),
Expression.Constant(testString)),
p).Compile();
Type predType = typeof(Func<,>).MakeGenericType(typeof(string), typeof(bool));
// Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
System.Reflection.MethodInfo anyMethod = typeof(Enumerable).GetMethod("Any", new Type[] {typeof(IEnumerable<string>), predType});
Expression anyCall = Expression.Call(
anyMethod,
Expression.Constant(strings),
Expression.Constant(predicate));
// test
Func<bool> a = (Func<bool>) Expression.Lambda(anyCall).Compile();
Thanks!
Try the following:
var p = Expression.Parameter(typeof(string), "x");
var strParam = Expression.Parameter(typeof(string), "str");
// x.Contains(str)
var boyd = Expression.Call(p, nameof(string.Contains), Type.EmptyTypes, strParam);
// x => x.Contains(str)
var lambdaExpression = Expression.Lambda<Func<string, bool>>(body, p);
var enumerableParam = Expression.Parameter(typeof(IEnumerable<string>), "e");
// e.Any(x => x.Contains(str))
var anyCall = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new[]{ typeof(string) }, enumerableParam, lambdaExpression);
// e => e.Any(x => x.Contains(str))
var anyLambda = Expression.Lambda<Func<IEnumerable<string>, string, bool>>(anyCall, enumerableParam, strParam)
// Func<IEnumerable<string>, string, bool>>
var complied = anyLambda.Compile();
// test
List<string> strings = new List<string> { "element1", "element2", "element3" };
var testString = "test";
var result = complied(strings, testString);
(Disclaimer: I am the author of the library in question.)
Using the ExpressionTreeToString library, available on NuGet, you can call the ToString extension method on an expression:
List<string> strings = new List<string> { "element1", "element2", "element3" };
Expression<Func<bool>> expr = () => strings.Any(x => x.Contains("test"));
Console.WriteLine(expr.ToString("Factory methods", "C#"));
which produces output like the following:
var strings = Parameter(
typeof(List<string>),
"strings"
);
var x = Parameter(
typeof(string),
"x"
);
Lambda(
Call(
typeof(Enumerable).GetMethod("Any", new[] { typeof(IEnumerable<string>), typeof(Func<string, bool>) }),
strings,
Lambda(
Call(x,
typeof(string).GetMethod("Contains", new[] { typeof(string) }),
Constant("test")
),
x
)
)
)
and then modify it for your needs. For example, you could replace the strings variable with a ConstantExpression wrapping the list.
I am testing dynamic expressions for a list but I have the following error.
CODE
List<DTOPreuab> modelolista = new List<DTOPreuab>();
modelolista.Add(new DTOPreuab { id = 1, nom = "test1" });
modelolista.Add(new DTOPreuab { id = 2, nom = "test2" });
IQueryable<DTOPreuab> queryableData = modelolista.AsQueryable<DTOPreuab>();
ParameterExpression pe = Expression.Parameter(typeof(string), "nom");
GetStringMethod
Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant("test2");
Expression e1 = Expression.Equal(left, right);
var lambda = Expression.Lambda<Func<string, bool>>(e1, pe);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { typeof(IEnumerable<DTOPreuab>) },
queryableData.Expression,
lambda
);
IQueryable<string> results = queryableData.Provider.CreateQuery<string>(whereCallExpression);
var response = results.ToArray();
Error Image
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 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;