C# Expression trees - combine multiple method calls - c#

I'm using .Net Core and expression trees.
I have a Product class, where LstProp property contains list of values separated by ';', like "val1;val2;val3;":
public class Product
{
// actually contains list of values separated by ';'
public string LstProp{get; set;}
}
And I want to filter Products by this property and contains any condition using expression trees.
I've try this, but it doesn't work.
var value="val1;val2"
var productItem = Expression.Parameter(typeof(Product), "product");
var prop = Expression.Property(productItem, "LstProp");
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var values = value.ToString().Split(';');
Expression predicate = null;
foreach (var val in values)
{
var listPropExpression = Expression.Constant(val);
var containsExpresion=Expression.Call(listPropExpression, method, property);
predicate = predicate != null ? Expression.Or(predicate, containsExpresion) : containsExpresion;
}
So I'm trying to combine call of Contains function for each value in the list, but getting error about "No conversion between BinaryExpression and MethodCallExpression".
How could I combine multiple method calls with expression trees?

I have found solution.
I got two problems:
Incorrect order of parameters.
Should be Expression.Call(property, method, listPropExpression); instead Expression.Call(listPropExpression, method, property);
Main problem was solved with simple cast:
predicate = predicate != null ? (Expression) Expression.Or(predicate, containsExpresion) : containsExpresion;
As a Result I get expression like product=>product.LstProp.Contains("val1") Or product.LstProp.Contains("val2")

Related

Add second group by key field to existing Lambda Expression

I have a Group by expression that I am dynamically creating for use in a LINQ query. Currently, to construct the expression, I use the following code:
var arg = Expression.Parameter(typeof(T), helper.getName());
var prop = Expression.Property(arg, "customerType");
var body = Expression.Convert(prop, typeof(object));
var lambda = Expression.Lambda<Func<Contact, object>>(body, arg);
var keySelector = lambda.Compile();
I then use the keySelector in the GroupBy for my LINQ query. My question is, if I wanted to add a second grouping criteria to this expression, say "salesStage", how would I add that to this existing expression?
You have a problem, because what the compiler does on a regular GroupBy call is generate a new anonymous type with the properties you define. If the type doesn't exist, we cannot create an expression creating an object of the type.
However, given that you are using this for LINQ-to-Objects, we can use the Tuple<> type to generate the grouping key. Hopefully you do not need to group on more than 8 parameters.
Here is a generic function to generate the grouping function:
static Func<T, object> BuildGrouper<T>(IEnumerable<string> properties) {
var arg = Expression.Parameter(typeof(T), helper.getName());
// This is the list of property accesses we will be using
var parameters = properties.Select(propName => Expression.Property(arg, propName)).ToList();
// Find the correct overload of Tuple.Create.
// This will throw if the number of parameters is more than 8!
var method = typeof(Tuple).GetMethods().Where(m => m.Name == "Create" && m.GetParameters().Length == parameters.Count).Single();
// But it is a generic method, we need to specify the types of each of the arguments
var paramTypes = parameters.Select(p => p.Type).ToArray();
method = method.MakeGenericMethod(paramTypes);
// Invoke the Tuple.Create method and return the Func
var call = Expression.Call(null, method, parameters);
var lambda = Expression.Lambda<Func<T, object>>(call, arg);
return lambda.Compile();
}

where can I find a good example of using linq & lambda expressions to generate dynamic where and orderby sql?

where can I find a good example of using linq & lambda expressions to generate dynamic sql?
For example, I need a method to take these parameters
GoupOperator ('And' or 'OR')
A list of objects each with the following parameters:
SearchColumn.
SearchValue.
SearchOperator (equals, contains, does not equal ...)
And another method to orderby any particular column ascending or descending
If this question has been properly answered before , I will gladly delete it - none of previous answers Ive seen are comprehensive enough for a person new to linq expressions to plug into an existing application with little trouble --thanks
I found a couple of linq extension methods (generic Where and OrderBy methods written by Ilya Builuk that take the columnname, search value and search operations plus a grouping operator) on codeproject here that shows how to do this using asp.net mvc.
the methods construct a dynamic expression tree -very elegant solution.
Since I had started using a traditional asmx web service, I used his helpers in my project and just made a few changes to get it running here -
Here are the 2 methods
public static class LinqExtensions
{
/// <summary>Orders the sequence by specific column and direction.</summary>
/// <param name="query">The query.</param>
/// <param name="sortColumn">The sort column.</param>
/// <param name="ascending">if set to true [ascending].</param>
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string sortColumn, string direction)
{
string methodName = string.Format("OrderBy{0}",
direction.ToLower() == "asc" ? "" : "descending");
ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
MemberExpression memberAccess = null;
foreach (var property in sortColumn.Split('.'))
memberAccess = MemberExpression.Property
(memberAccess ?? (parameter as Expression), property);
LambdaExpression orderByLambda = Expression.Lambda(memberAccess, parameter);
MethodCallExpression result = Expression.Call(
typeof(Queryable),
methodName,
new[] { query.ElementType, memberAccess.Type },
query.Expression,
Expression.Quote(orderByLambda));
return query.Provider.CreateQuery<T>(result);
}
public static IQueryable<T> Where<T>(this IQueryable<T> query,
string column, object value, WhereOperation operation)
{
if (string.IsNullOrEmpty(column))
return query;
ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
MemberExpression memberAccess = null;
foreach (var property in column.Split('.'))
memberAccess = MemberExpression.Property
(memberAccess ?? (parameter as Expression), property);
//change param value type
//necessary to getting bool from string
ConstantExpression filter = Expression.Constant
(
Convert.ChangeType(value, memberAccess.Type)
);
//switch operation
Expression condition = null;
LambdaExpression lambda = null;
switch (operation)
{
//equal ==
case WhereOperation.Equal:
condition = Expression.Equal(memberAccess, filter);
lambda = Expression.Lambda(condition, parameter);
break;
//not equal !=
case WhereOperation.NotEqual:
condition = Expression.NotEqual(memberAccess, filter);
lambda = Expression.Lambda(condition, parameter);
break;
//string.Contains()
case WhereOperation.Contains:
condition = Expression.Call(memberAccess,
typeof(string).GetMethod("Contains"),
Expression.Constant(value));
lambda = Expression.Lambda(condition, parameter);
break;
}
MethodCallExpression result = Expression.Call(
typeof(Queryable), "Where",
new[] { query.ElementType },
query.Expression,
lambda);
return query.Provider.CreateQuery<T>(result);
}
}
Below is how I used these methods, the return object is simply a custom object that supplies data to a client side grid
public class Service1 : System.Web.Services.WebService
{
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public JgGrid SearchGrid(int rows, int page, string sidx, string sord,string filters)
{
AdvWorksDataContext dc = new AdvWorksDataContext();
JavaScriptSerializer serializer = new JavaScriptSerializer();
filters f = serializer.Deserialize<filters>(filters);
var p = dc.vProductAndDescriptions.AsQueryable();
if (f.groupOp == "AND")
foreach (var rule in f.rules)
p = p.Where<vProductAndDescription>(
rule.field, rule.data,
(WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op)
);
else
{
//Or
var temp = (new List<vProductAndDescription>()).AsQueryable();
foreach (var rule in f.rules)
{
var t = p.Where<vProductAndDescription>(
rule.field, rule.data,
(WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op)
);
temp = temp.Concat<vProductAndDescription>(t);
}
p = temp;
}
p = p.OrderBy<vProductAndDescription>(sidx, sord);
return new JgGrid(page, p, rows);
}
}
For cases where I have many columns that need dynamic query composition I use Dynamic Linq. This is a library written as an example for .net 3.5 and it illustrates how you can write linq extensions that operate on the expression tree.
It can also be used for composing dynamic queries based on strings received from the client, such as column names, sorting, etc.
Here's a link a to an article posted by Scott Guthrie
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
In the article you will find the links to the examples library which contains the source code for the Dynamic Linq library.
It seems to me that you're trying to build a linq provider...
Try to check this tutorial serie on how to implement a custom Linq to SQL provider : LINQ: Building an IQueryable provider series

Entity Framework 4: convert a string condition to a lambda expression?

I want to accept a string array of where conditions from the client like field == value.
It would be really nice to create a specification object that could accept the string in the constructor and output a lambda expression to represent the Where clause. For example, I could do the following:
var myCondition = new Specification<Product>( myStringArrayOfConditions);
var myProducts = DB.Products.Where( myCondition);
How could you turn "name == Jujyfruits" into DB.Products.Where(p => p.name == "JujyFruits")?
You can use
Reflection to get the Property Product.name from the string name and
the LINQ Expression class to manually create a lambda expression.
Note that the following code example will only work for Equals (==) operations. However, it is easy to generalize to other operations as well (split on whitespace, parse the operator and choose the appropriate Expression instead of Expression.Equal).
var condition = "name == Jujyfruits";
// Parse the condition
var c = condition.Split(new string[] { "==" }, StringSplitOptions.None);
var propertyName = c[0].Trim();
var value = c[1].Trim();
// Create the lambda
var arg = Expression.Parameter(typeof(Product), "p");
var property = typeof(Product).GetProperty(propertyName);
var comparison = Expression.Equal(
Expression.MakeMemberAccess(arg, property),
Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(comparison, arg).Compile();
// Test
var prod1 = new Product() { name = "Test" };
var prod2 = new Product() { name = "Jujyfruits" };
Console.WriteLine(lambda(prod1)); // outputs False
Console.WriteLine(lambda(prod2)); // outputs True
About the constructor thing: Since Func<T, TResult> is sealed, you cannot derive from it. However, you could create an implicit conversion operator that translates Specification<T> into Func<T, bool>.
We've recently discovered the Dynamic LINQ library from the VS2008 sample projects. Works perfectly to turn string based "Where" clauses into expressions.
This link will get you there.
You need to turn your search term into a predicate. Try something like the following:
string searchString = "JujyFruits";
Func<Product, bool> search = new Func<Product,bool>(p => p.name == searchString);
return DB.Products.Where(search);

c# List<string> to Lambda Expression with starter example: Refactor to handle the List

I have this:
List<string> fields;
fields[0] = "firstFieldName";
fields[1] = "secondFieldName";
...
fields[n] = "nthFieldName";
I want to get this:
var selector = p => new {p.firstField, p.secondField, ..., p.nthFieldName}
// selector is of type Expression<Func<Entity, object>>
GoofBallLogic had this code that was simliar, ending up with p => p.column
// Entity is an object in a diagram from Entity Framework 4
var p = Expression.Parameter(typeof(Entity, "p");
var selector = Expression.Lambda<Func<Entity, string>(
Expression.Property(p, columnToGroupBy), p );
EDIT: What I am trying to accomplish
I have a "generic" Repository:
public class Repository<E, C> : IRepository<E,C>
{
private C _dc {get;set;} // ObjectContext (Entity Framework 4)
private string _entityName {get;set;}
public string entityKeyName {get;private set;}
public List<string> entityKeys {get;private set;}
public Expression<Func<E, object>> entityKey {get;private set;}
private EntityContainer _containerName {get;set;}
public Repository(C myDC)
{ _dc = myDC; // TODO: check for null
// Name of "this" ObjectContext
_containerName = _dc.MetadataWorkspace.GetEntityContainer(
_dc.DefaultContainerName, DataSpace.CSpace);
// Name of "this" Entity
_entityName = _containerName.BaseEntitySets
.Where(p => p.ElementType.Name == typeof (E).Name)
.Select( p => p.Name).FirstOrDefault();
// String list of the keys
entityKeys = _containerName
.BaseEntitySets.First(meta => meta.ElementType.Name ==
typeof(E).Name)
.ElementType.KeyMembers.Select(k => k.Name).ToList();
// Thanks Jon Skeet for this cool comma sep list formula
entityKeyName = string.Join(",", entityKeys.ToArray() );
entityKey = Expression.Lambda<Func<E, object>> ...
What to do to set entityKey as an object that can be used in
an OrderBy statement since Linq to Entities requires
ordering a set before doing a .Skip().Take()
Edit:
Amazingly, Orderby can take this:
p => "field1,field2,field3"
Which allows my code to execute but doesn't actually order the items by the field values. It's a first step in TDD I guess: use a literal.
I found this an interesting problem and took some time to figure it out, and found a relatively easy way to do it.
Anyhow, here's an example on how to do a single field sort (i'll use your first field), if you want to sort on more fields you'll have to create expressions for them too and use .ThenBy(xxx) after the usual OrderBy(xxx).
// Create a parameter which passes the object
ParameterExpression param = Expression.Parameter(typeof(E), "a");
// Create body of lambda expression
Expression body = Expression.PropertyOrField(param, fieldname);
// Create lambda function
Expression<Func<E, string>> exp = Expression.Lambda<Func<E, string>>(body, param);
// Compile it so we can use it
Func<E, string> orderFunc = exp.Compile();
Now you can do an OrderBy(orderFunc) and it'll sort the list by the property named in fieldname. Only downside being it only works for string fields (return value of expression). Could probably work around that too though.
Fixed to work with any IComparable type:
// Create a parameter which passes the field
ParameterExpression param = Expression.Parameter(typeof(E), "a");
// Create body of lambda expression
Expression body = Expression.TypeAs(Expression.PropertyOrField(param, fieldname), typeof(IComparable));
// Create lambda function
Expression<Func<E, IComparable>> exp = Expression.Lambda<Func<E, IComparable>>(body, param);
// Compile it so we can use it
Func<E, IComparable> orderFunc = exp.Compile();
You cannot do this easily because you cannot construct a new expression for a type that doesn’t exist at runtime. (You can have anonymous types in C# because the C# compiler creates the type for you.)
If you want to do it the really hard way, you could generate a dynamic assembly and actually create the type you need. There is a short example here.
I suspect that there is an easier way, though. We would need to know what your goal is (what you need this expression tree for), which you haven’t stated.
From your edited question, it appears that you just want to be able to order by multiple keys. That is easily possible simply by using .OrderBy() followed by .ThenBy(). I’m assuming that you are using an IQueryable<E> here:
IQueryable<E> query = ...;
IOrderedQueryable<E> ordered = null;
foreach (var key in entityKeys)
{
// Code from Doggett to construct the lambda expression for one step
ParameterExpression param = Expression.Parameter(typeof(E), "a");
var body = Expression.TypeAs(
Expression.PropertyOrField(param, key),
typeof(IComparable));
var exp = Expression.Lambda<Func<E, IComparable>>(body, param);
if (ordered == null)
ordered = query.OrderBy(exp);
else
ordered = ordered.ThenBy(exp);
}
var finalQuery = (ordered ?? query).Skip(n).Take(m);

Querying Entity with LINQ using Dyanmic Field Name

I have created a dynamic search screen in ASP.NET MVC. I retrieved the field names from the entity through reflection so that I could allow the user to choose which fields they wanted to search on instead of displaying all fields in the view.
When the search result is Posted back to the controller, I receive a FormCollection containing the FieldName and the Value. I don't know how many fields are being searched on, and the FormCollection only contains fields that were chosen by the user.
I want to be able to now take that field name and apply that to my LINQ statement when I query the database for example:
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
List<People> searchResults = new List<People>();
foreach (string key in fieldValueDictionary.Keys)
{
searchResults.Add(entities.People.Where(p => p.<use the key string as the fieldName> == fieldValueDictionary[key]));
}
return searchResults;
}
Where I have "use the key string as the fieldName" it would be like p => p.FirstName == fieldValueDictionary[key] where key = "FirstName". I've tried and failed to use Lambda Expression Trees, and have had a little success with Dynamic LINQ. The only other alternative is to do something like:
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
IQueryable<People> results = entities.People;
foreach (string key in fieldValueDictionary.Keys)
{
switch (k)
{
case "FirstName": results = results.Where(entities.People.Where(p => p.FirstName == k);
case "LastName": results = results.Where(entities.People.Where(p => p.LastName == k);
// Repeat for all 26 fields in table
}
}
return results.ToList<People>();
}
UPDATE: I've done research into Lambda Expression Trees through the following posts:
dynamically create lambdas expressions + linq + OrderByDescending
Parameter problem with Expression.Lambda()
LINQ: Passing lambda expression as parameter to be executed and returned by method
I've gotten as far as getting a lambda to output the following: "p => p.FirstName", but I can't get this to work in a where. Any Suggestions? My code is below:
MemberInfo member = typeof(People).GetProperty("FirstName");
ParameterExpression cParam = Expression.Parameter(typeof(People), "p");
Expression body = Expression.MakeMemberAccess(cParam, member);
var lambda = Expression.Lambda(body, cParam);
After a lot more trial and error and searching I accidentally found another SO post that covers the same issue:
InvalidOperationException: No method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied arguments
Here is my modified code that works:
IQueryable query = entities.People;
Type[] exprArgTypes = { query.ElementType };
string propToWhere = "FirstName";
ParameterExpression p = Expression.Parameter(typeof(People), "p");
MemberExpression member = Expression.PropertyOrField(p, propToWhere);
LambdaExpression lambda = Expression.Lambda<Func<People, bool>>(Expression.Equal(member, Expression.Constant("Scott")), p);
MethodCallExpression methodCall = Expression.Call(typeof(Queryable), "Where", exprArgTypes, query.Expression, lambda);
IQueryable q = query.Provider.CreateQuery(methodCall);
With some hopefully pretty easy modifications, I should be able to get this to work with any type.
Thanks again for your answers Ani & John Bowen
Have you tried getting the value from PropertyInfo?
entities.People.Where(p => (p.GetType().GetProperty(key).GetValue(p, null) as string) == fieldValueDictionary[key])
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
return !fieldValueDictionary.Any()
? entities.People
: entities.People.Where(p => fieldValueDictionary.All(kvp => PropertyStringEquals(p, kvp.Key, kvp.Value)))
.ToList();
}
private bool PropertyStringEquals(object obj, string propertyName, string comparison)
{
var val = obj.GetType().GetProperty(propertyName).GetValue(obj, null);
return val == null ? comparison == null : val.ToString() == comparison; ;
}

Categories

Resources