I have been trying to write a method that will build an expression based on types and parameters passed in. Currently the method is:
// tuple: CollectionName, ClassName, PropertyName
public Expression<Func<T, bool>> BuildCollectionWithLike<T, TSub>(Dictionary<Tuple<string, string, string>, string> properties)
{
// each one should generate something like:
// x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
try
{
var type = typeof(T);
List<Expression> expressions = new List<Expression>();
var xParameter = Expression.Parameter(typeof(T), "x");
foreach (var key in properties.Keys)
{
var collectionType = typeof(TSub);
var yParameter = Expression.Parameter(typeof(TSub), "y");
var propertyExp = Expression.Property(yParameter, key.Item3);
MethodInfo methodContains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(properties[key], typeof(string));
var containsMethodExp = Expression.Call(propertyExp, methodContains, someValue);
var whereProperty = type.GetProperty(key.Item1);
var wherePropertyExp = Expression.Property(xParameter, whereProperty);
Func<IEnumerable<T>, Func<T, bool>, IEnumerable<T>> whereDelegate = Enumerable.Where;
MethodInfo whereMethodInfo = whereDelegate.Method;
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
expressions.Add(whereMethodExp);
}
Expression final = expressions.First();
foreach (var expression in expressions.Skip(1))
{
final = Expression.Or(final, expression);
}
Expression<Func<T, bool>> predicate =
(Expression<Func<T, bool>>)Expression.Lambda(final, xParameter);
return predicate;
}
catch (Exception ex)
{
return null;
}
}
However at this line:
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
I get this exception:
Expression of type 'System.Collections.Generic.ICollection`1[Model.ProductPreviousSku]'
cannot be used for parameter of type 'System.Collections.Generic.IEnumerable`1[Model.Product]'
of method 'System.Collections.Generic.IEnumerable`1[Model.Product] Where[Product](System.Collections.Generic.IEnumerable`1[Model.Product], System.Func`2[Model.Product,System.Boolean])'"
My class Model.Product has a property of type ICollection called PreviousSKUs.
I have a class called ProductPreviousSku which has a property of type string called PreviousSku.
As per my comment in at the start of the method I am trying to get this method to be able to construct an expression inside the foreach loop that looks like:
x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
I'm struggling to get passed this error at the moment so any help would be fantastic !
Related
I want to create an extension method for a LINQ expression but I'm stuck. What I need is just to create a method which will add a specific Where clause to a Queryable. Something like:
var hierarchy = "a string";
Session.Query<SomeClass>.Where(x => x.Layer.Hierarchy.StartsWith(hierarchy) ||
x.Layer.Hierarchy == hierarchy);
to become:
var hierarchy = "a string";
Session.Query<SomeClass>.LayerHierarchy(x => x.Layer, hierarchy);
And do that Where logic inside. So basicly the extension method LayerHierarchy() is running over the Queryable of T but the subject is of type Layer:
public static IQueryable<T> LayerHierarchy<T>(this IQueryable<T> query,
Expression<Func<T, Layer>> layer,
string hierarchy)
{
var parameterExp = Expression.Parameter(typeof(Layer), "layer");
var propertyExp = Expression.Property(parameterExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
var startsWith = Expression.Lambda<Func<Layer, bool>>(methodExpStartsWith, parameterExp);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var equals = Expression.Lambda<Func<Layer, bool>>(methodExpEquals, parameterExp);
return query
.Where(startsWith)
.Where(equals);
}
Everything works fine above the return line. It complains that...
Cannot convert from System.Linq.Expressions.Expression<System.Func<Layer, bool>> to System.Linq.Expressions.Expression<System.Func<T, int, bool>>
when trying to pass the expressions to query.Where() method. How can I fix it?
Well, the problem is how you are creating the Lambdas. They should begin from T, not from Layer:
var startsWith = Expression.Lambda<Func<T, bool>>(methodExpStartsWith, parameterExp);
var equals = Expression.Lambda<Func<T, bool>>(methodExpEquals, parameterExp);
However, in order for this to work, you are missing one more PropertyExpression.
Your query now looks like:
(Layer)x => x.Hierarchy.StartsWith(...)
When, what you want is this:
(T)x => x.Layer.Hierarchy.StartsWith(...)
So, use this instead:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
Your logic should change a little though, since two .Where will generate an AND condition between them, and it seems like you want one of them to be true (StartsWith or Equals), so:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var orElseExp = Expression.OrElse(methodExpStartsWith, methodExpEquals);
var orElse = Expression.Lambda<Func<T, bool>>(orElseExp, parameterExp);
return query.Where(orElse);
I'm trying to figure out for a IQueryable how I can build a csv file by dynamically selecting objects as strings.
for example:
I read this about dynamically selecting properties of a T ...
LINQ : Dynamic select
That would allow me to do something like this ...
var data = new List<T> { items };
var fields = new string[] { "Field1", "Field2", "Field3" };
// build row strings
var rows = set.Select(BuildRowObjectExpression<T, ProjectionOfT>(fields))
.Select(i => Serialise<ProjectionOfT>(i));
string Serialise<T>(T i, string separator)
{
var properties = typeof(T).GetProperties();
var values = new List<string>();
foreach (var p in properties)
values.Add(p.GetValue(i).ToString());
return string.Join(separator, values);
}
Func<T, Tout> BuildRowObjectExpression<T, Tout>(string[] fields)
{
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = fields.Select(o => {
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new T { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new T { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, string>>(xInit, xParameter);
// compile to Func<T, string>
return lambda.Compile();
}
What I was wondering however is:
How do I build this as an expression / func that I can use with an IQueryable to do something like this
// this would build me a string array from the specified properties
// in a collection of T joining the values using the given separator
var results = data.Select(i => BuildString(fields, "|")).ToArray();
I would ideally like to use this with an entity set.
String conversion/concatenation is not a database job. You'd better keep the two parts separate - data retrieval in database query and data transformation in memory query.
For instance, you can use the following custom extensions methods:
public static class Extensions
{
public static IQueryable<T> Select<T>(this IQueryable source, string[] fields)
{
var parameter = Expression.Parameter(source.ElementType, "o");
var body = Expression.MemberInit(
Expression.New(typeof(T)),
fields.Select(field => Expression.Bind(
typeof(T).GetProperty(field),
Expression.PropertyOrField(parameter, field))
)
);
var selector = Expression.Lambda(body, parameter);
var expression = Expression.Call(
typeof(Queryable), "Select", new[] { parameter.Type, body.Type },
source.Expression, Expression.Quote(selector)
);
return source.Provider.CreateQuery<T>(expression);
}
public static IEnumerable<string> Serialize<T>(this IEnumerable<T> source, string separator)
{
var properties = typeof(T).GetProperties();
return source.Select(item => string.Join(separator, properties.Select(property => property.GetValue(item))));
}
}
like this
var results = db.Data.Select<ProjectionOfT>(fields).Serialize("|");
If you want to avoid the ProjectionOfT class, there is no easy way to do that since it requires dynamic runtime class generation, so you'd better resort to System.Linq.Dynamic package.
I need make custom orderby for enum. I try use SwitchExpression:
public static IQueryable<T> MyOrderByEnum<T>(this IQueryable<T> source, string propName, Type enumType)
{
var type = typeof (T);
var property = type.GetProperty(propName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.Property(parameter, property);
var enumValues = Enum.GetValues(enumType);
var switchCases = new SwitchCase[enumValues.Length];
int i = 0;
foreach (var val in enumValues)
{
switchCases[i] = Expression.SwitchCase(
Expression.Constant(val.ToDisplay()),
Expression.Constant(val)
);
i++;
}
var switchExpr1 =
Expression.Switch(
propertyAccess,
Expression.Constant(""),
switchCases
);
var orderByExp1 = Expression.Lambda(switchExpr1, parameter);
MethodCallExpression resultExp = Expression.Call(typeof (Queryable), "OrderBy", new[] {type, orderByExp1.Body.Type}, source.Expression, orderByExp1);
return (IOrderedQueryable<T>) source.Provider.CreateQuery(resultExp);
}
But when I execute
filtered.MyOrderBy("field1", typeof(FieldState)).ToList();
I get error:
Unknown LINQ expression of type 'Switch'.
Is there another way to make order expression that will translate into sql construction "CASE WHEN ..."?
Try Expression.Condition (https://msdn.microsoft.com/en-us/library/bb340500%28v=vs.110%29.aspx)
I think that translates to CASE When if used in an anonymous projection
I am trying search each property value of an IQueryable collection of T against the value of a search query. I have the following function and would like to know how do I ALSO test for NOT NULL and CONTAINS together?
private Expression<Func<T, bool>> PropertySearch
{
get
{
// Object that is passed to the lambda expression
ParameterExpression instance = Expression.Parameter(typeof(T), "val");
Expression whereExpr = Expression.Constant(true); // default is val => True
var _properties = typeof(T).GetProperties();
foreach (var prop in _properties)
{
var query = _httpRequest["query"].ToLower();
var property = Expression.Property(instance, prop);
var toStringCall = Expression.Call(Expression.Call(
property,
"ToString",
new Type[0]),
typeof(string).GetMethod("ToLower", new Type[0]));
whereExpr = Expression.And(whereExpr,
Expression.Call(toStringCall, typeof(string).GetMethod("Contains"),
Expression.Constant(query)));
}
return Expression.Lambda<Func<T, bool>>(whereExpr, instance);
}}
I have created a search extensions nuget package that performs this type of check. For your example I would do something like the following.
Note, this is without an IDE so may have some errors
/* *** Start: These can be made private reaonly fields ***/
var comparisonExpr = Expression.Constant(StringComparison.OrdinalIgnoreCase);
var zeroExpression = Expression.Constant(0)
var nullExpression = Expression.Constant(null)
MethodInfo IndexOfMethod = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) });
/* *** End ***/
Expression finalExpression = null
ParameterExpression instance = Expression.Parameter(typeof(T), "val");
var _properties = typeof(T).GetProperties();
var query = _httpRequest["query"].ToLower();
var queryExpr = Expression.Constant(query);
foreach (var prop in _properties)
{
//Get property
var propertyExpr = Expression.Property(instance, prop);
//Get property as string
var propStringExpr = Expression.Call(property, "ToString", new Type[0]);
//Perform IndexOf call
var indexOfExpr = Expression.Call(propStringExpr,
IndexOfMethod,
queryExpr,
comparisonExpr);
// Check index of is greater than or equal to zero
var containsExpr = Expression.GreaterThanOrEqual(containsExpr, zeroExpression);
if(finalExpression == null)
{
finalExpression = containsExp;
}
else
{
finalExpression = Expression.AndAlso(containsExpr);
}
}
return Expression.Lambda<Func<T, bool>>(finalExpression, instance);
I've removed the need for ToLower() and instead used IndexOf with a string comparison type
If you want to see how I have achieved similar functionality, take a look at NinjaNye.SearchExtensions on Github
https://github.com/ninjanye/SearchExtensions
If you wanted to search a collection of IQueryable you could use NinjaNye.SearchExtensions as follows
string query = _httpRequest["query"];
var result = data.SearchAll().Containing(query);
This will search all string properties (not all properties as you have above) and return just those where any property mathes the search term.
Hope this helps
You could probably use PredicateBuilder so you don't have to mess with expression trees yourself.
i want to build a generic search window using linq to sql.
This is what i was trying to do:
class SearchWindow<T> : Form : Where T: class
{
public SearchWindow(Func<T, string> codeSelector,
Func<T, string> nameSelector)
{
var db = new DataContext();
var table = db.GetTable<T>();
var query = from item in table where
codeSelector(item).Contains(someText) &&
nameSelector(item).Contains(someOtherText)
select item;
}
}
And i was trying to use it like:
var searchWindow = new SearchWindow<SomeTable>(x => x.CodeColumn,
y => y.NameColumn).Show();
Bud saddly that doesn't work, i read about expression trees so i tried to do that with them, and i got:
public SearchWindow(codeColumn, nameColumn)
{
Table<T> table = db.GetTable<T>();
var instanceParameter = Expression.Parameter(typeof(T), "instance");
var methodInfo = typeof(string).GetMethod("Contains",
new Type[] { typeof(string) });
var codigoExpression = Expression.Call(Expression.Property(instanceParameter,
codeColumn),
methodInfo,
Expression.Constant("someText",
typeof(string)));
var nombreExpression = Expression.Call(Expression.Property(instanceParameter,
nameColumn),
methodInfo,
Expression.Constant("someOtherText",
typeof(string)));
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.And(codigoExpression, nombreExpression), instanceParameter);
var query = table.Where(predicate);
}
And to use it i need to do:
new SearchWindow<SomeTable>("codeColumn", "nameColumn");
But i don't like the approach to need to enter the column names as a string, is there any way to do it in a fashion similar to my first approach (in order to have intellisense and strong typing)?
Thank you for your help.
Untested, but something like:
static IQueryable<T> Search<T>(
IQueryable<T> source,
Expression<Func<T, string>> codeSelector,
Expression<Func<T, string>> nameSelector,
string code, string name)
{
var row = Expression.Parameter(typeof(T), "row");
var body = Expression.AndAlso(
Expression.Call(
Expression.Invoke(codeSelector, row),
"Contains", null,
Expression.Constant(code, typeof(string))),
Expression.Call(
Expression.Invoke(nameSelector, row),
"Contains", null,
Expression.Constant(name, typeof(string))));
var lambda = Expression.Lambda<Func<T, bool>>(body, row);
return source.Where(lambda);
}
You pass in your table (GetTable<T>) as the source, and lambdas to indicate the columns (x => x.CodeColumn / y => y.NameColumn etc).
Update; tested on LINQ-to-Objects, I'm hopeful it'll work on LINQ-to-SQL as well:
var data = new[] {
new { Code = "abc", Name = "def"},
new { Code = "bcd", Name = "efg"},
new { Code = "ghi", Name = "jkl"}
}.AsQueryable();
var filtered = Search(data, x => x.Code, x => x.Name, "b", "f");
var arr = filtered.ToArray();
Use PredicateBuilder- it'll do the heavy lifting for you.