I'm attempting to do a dynamic join in linq. Meaning that I only know at runtime what field the join will occur on.
I've done the following:
var itemParam = Expression.Parameter(typeof(E), "obj");
var entityAccess = Expression.MakeMemberAccess(Expression.Parameter(typeof(E), "obj"), typeof(E).GetMember(Field).First());
var lambda = Expression.Lambda(entityAccess, itemParam);
var q = dbSet.Join(context.Acl, lambda, acl => acl.ObjectID, (entity, acl) => new { Entity = entity, ACL = acl });
However this throws at compile time, even though lambda appears to be the right syntax telling me that it cannot convert from LambdaExpression to Expression<System.Func<E, int>>.
How do I get it to create the right expression dynamically that uses my field (i.e. property "Field" above in the typeof(E).GetMember(Field).First()) line?
Use Expression.Lambda<TDelegate>, so that you end up with the line
// obj => obj.Field
var lambda = Expression.Lambda<Func<E, int>>(entityAccess, itemParam);
Update
As per your comment, the reason the expression fails is because you are using two different parameters. You define itemParam, but then do not use it in Expression.MakeMemberAccess
Try the following instead:
// obj
var itemParam = Expression.Parameter(typeof(E), "obj");
// obj.Field
var entityAccess = Expression.MakeMemberAccess(itemParam, typeof(E).GetMember(Field).First());
Related
I use Entity Framework 6 and don't use LINQKit.
Let's say we have UI where user can filter clients only by Name OR only by Surname OR by Name and Surname (it's extremely simplified but depicts my situation).
If I use static filter - it's working:
Expression<Func<Client, Boolean>> staticFilter = (c) => c.Name.Equals(someName) && c.Surname.Equals(someSurname);
var filteredClientsStaticFilter = context.Clients.Where(staticFilter).ToList();
But if I try to create the same filter dynamically:
Expression<Func<Client, Boolean>> nameFilter = (c) => c.Name.Equals(someName);
Expression<Func<Client, Boolean>> surnameFilter = (c) => c.Surname.Equals(someSurname);
var expr = Expression.And(nameFilter.Body, surnameFilter.Body);
var dynamicFilter = Expression.Lambda<Func<Client, Boolean>>(expr, nameFilter.Parameters[0]);
var filteredClientsDynamicFilter = context.Clients.Where(dynamicFilter).ToList();
- it fails with exception:
The parameter 'u' was not bound in the specified LINQ to Entities query
expression
But I need to create filter dynamically because it depends on user which filter condition he choose.
So how can I build dynamic filter correctly?
The (c) parameter of your both expressions are not the same, see the answer to this SO question.
You can get the expressions to use the same type parameter by using the Expression library.
First, create the type parameter:
var typeParameter = Expression.Parameter(typeof(Client), "c");
...then create the equal expressions by using the declared type parameter:
var nameProperty = Expression.Property(typeParameter, nameof(Client.Name));
var nameExpression = Expression.Equal(nameProperty, Expression.Constant(someName));
and:
var surnameProperty = Expression.Property(typeParameter, nameof(Client.Surname));
var surnameExpression = Expression.Equal(surnameProperty, Expression.Constant(someSurname));
You can then use the type parameter to create your filter and execute your query:
var expr = Expression.And(nameExpression, surnameExpression);
var dynamicFilter = Expression.Lambda<Func<Client, bool>>(expr, typeParameter);
var filteredClientsDynamicFilter = context.Clients.Where(dynamicFilter).ToList();
I'm trying to write a generic wildcard Search for the ServiceStack.OrmLite.SqlExpressionVisitor that has the following signature:
public static SqlExpressionVisitor<T> WhereWildcardSearch<T> (this SqlExpressionVisitor<T> ev, Expression<Func<T,string>> field, string search)
where ev is the rest of the filter, field is the getter for the field to search by and search is the entered term.
Normally (non-generic) I would write the following:
if(search.StartsWith('*') && search.EndsWith('*'))
ev = ev.Where(x => x.foo.Contains(search.Trim('*')));
and of course also variants for x.foo.StartsWith or EndsWith.
Now I am searching for something like (pseudocode:)
ev = ev.Where(x => field(x).Contains(search.Trim('*')));
Of course I can't compile and call the expression directly, as this should be translated to Sql using Linq2Sql.
This is my code so far:
var getFieldExpression = Expression.Invoke (field, Expression.Parameter (typeof (T), "getFieldParam"));
var searchConstant = Expression.Constant (search.Trim('*'));
var inExp = Expression.Call (getFieldExpression, typeof(String).GetMethod("Contains"), searchConstant);
var param = Expression.Parameter (typeof (T), "object");
var exp = Expression.Lambda<Func<T, bool>> (inExp, param);
ev = ev.Where (exp);
Please don't tell me that I should directly write SQL with $"LIKE %search%" or something - I know that there are other ways, but solving this would help my understanding of Linq and Expressions in general and it bugs me when I can't solve it.
Here is how it can be done (I think it will be clear for you without much additional explanations what you did wrong, but if not - feel free to request a clarification):
// extract property name from passed expression
var propertyName = ((MemberExpression)field.Body).Member.Name;
var param = Expression.Parameter(typeof(T), "object");
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// object.FieldName.Contains(searchConstant)
var inExp = Expression.Call(Expression.PropertyOrField(param, propertyName), contains, searchConstant);
// object => object.FieldName.Contains(searchConstant)
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In response to comment. You have two expression trees: one is being passed to you and another one which you are building (exp). In this simple case they both use the same number of parameters and those parameters are of the same type (T). In this case you can reuse parameter from field expression tree, like this:
// use the same parameter
var param = field.Parameters[0];
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// note field.Body here. Your `field` expression is "parameter => parameter.Something"
// but we need just "parameter.Something" expression here
var inExp = Expression.Call(field.Body, contains, searchConstant);
// pass the same parameter to new tree
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In more complicated cases you might need to use ExpressionVisitor to replace parameters in one expression tree to reference to parameters from another (final) expression tree.
I have a DataClassesDataContext containing a group of tables, and I am trying to do lambda expression filtering dynamically using only the name of the tables and the names of the fields. Basically I want to find for each table if a row with a specific ID already exists.
If I knew the table ahead of time, I would use :
if (dataClassesDataContext.MYTABLEXs.SingleOrDefault(m => m.MYTABLEX_ID == MyId))
DoExists();
But as I am getting tables names MYTABLEX and MYTABLEY (and fields names MYTABLEX_ID and MYTABLEY_ID) as strings on the fly, I am trying to build the above filter at runtime.
I can access the table dynamically using :
Type tableType = Type.GetType(incommingtableName); // incommingtableName being looped over MYTABLEX, MYTABLEY , ...
var dbTable = dataClassesDataContext.GetTable(tableType);
But then I am stuck. How can I build a lambda expression that will behave something like :
if (dbTable.SingleOrDefault(m => m.incommingtableName_id == MyId))
DoExists();
Any idea ?
You can build an expression in runtime. And also you would need to have generic version of SingleOrDefault method. Here is example:
Type tableType = typeof (incommingtableName); // table type
string idPropertyName = "ID"; // id property name
int myId = 42; // value for searching
// here we are building lambda expression dynamically. It will be like m => m.ID = 42;
ParameterExpression param = Expression.Parameter(tableType, "m");
MemberExpression idProperty = Expression.PropertyOrField(param, idPropertyName);
ConstantExpression constValue = Expression.Constant(myId);
BinaryExpression body = Expression.Equal(idProperty, constValue);
var lambda = Expression.Lambda(body, param);
// then we would need to get generic method. As SingleOrDefault is generic method, we are searching for it,
// and then construct it based on tableType parameter
// in my example i've used CodeFirst context, but it shouldn't matter
SupplyDepot.DAL.SupplyDepotContext context = new SupplyDepotContext();
var dbTable = context.Set(tableType);
// here we are getting SingleOrDefault<T>(Expression) method and making it as SingleOrDefault<tableType>(Expression)
var genericSingleOrDefaultMethod =
typeof (Queryable).GetMethods().First(m => m.Name == "SingleOrDefault" && m.GetParameters().Length == 2);
var specificSingleOrDefault = genericSingleOrDefaultMethod.MakeGenericMethod(tableType);
// and finally we are exexuting it with constructed lambda
var result = specificSingleOrDefault.Invoke(null, new object[] { dbTable, lambda });
As possible optimization constructed lambda can be cached, so we wont need to build it each time, but it should work the same
I'm drawing inspiration from this question:
Convert Linq to Sql Expression to Expression Tree
The original poster asked how to convert this to an Expression tree and got a good answer which can be seen in the above link.
List<Region> lst = (from r in dc.Regions
where r.RegionID > 2 && r.RegionDescription.Contains("ern")
select r).ToList();
How would I got about making a property with a get method that returns a bool that uses the ExpressionTree? I'd like to be able to do something like this (obviously I don't need the == true):
List<Region> lst = (from r in dc.Regions
where (r.AwesomeProperty == true)
select r).ToList();
How would I go about defining AwesomeProperty?
You would define AwesomeProperty just like any other property on your LINQ to SQL object. Assuming it is typed as bool (since you compare it to true), you would build the Where query like this:
// Build the parameter to the where clause predicate and access AwesomeProperty
var regionParameter = Expression.Parameter(typeof(Region), "region");
var awesomeProperty = Expression.Property(regionParameter, "AwesomeProperty");
// Build the where clause predicate using the AwesomeProperty access
var predicate = Expression.Lambda<Func<Region, bool>>(awesomeProperty);
// Get the table, which serves as the base query
var table = dc.Regions.AsQueryable();
// Call the Where method using the predicate and the table as the base query
var whereCall = Expression.Call(
typeof(Queryable),
"Where",
new[] { table.ElementType },
table.Expression,
predicate);
// Get an IQueryable<Region> which executes the where call on the table
var query = table.Provider.CreateQuery<Region>(whereCall);
var results = query.ToList();
At point (3) in my code I have defined a query called query1 in which I defined a .Where lambda expression. This query is in some way dynamic but still contains static elements, it always refers to the Type Employee and its (int) property ClientID.
Now I very much like to make the refering to the type and its property dynamic, based on the method parameters which by example are shown below point (1).
What I tried to so far is making the static part of the query defined under point (3) fully dynamic by replacing it with a more elaborate expression tree as written down in (4), (5) & (6). But when I try to add everything together it says I call .Where with wrong parameters. I don't know how to call .Where with the right parameters in order to create a fully dynamic select.
Does someone know to solve this problem? I have spent a day searching and haven't found a solution so far.
dsMain domainService = new dsMain();
//(1)i want to rewrite the following four variables to method-parameters
Type entityType = typeof(Employee);
String targetProperty = "ClientID";
Type entityProperty = typeof(Employee).GetProperty(targetProperty).PropertyType;
int idToDelete = 5;
//(2)create expression-function: idToDelete == entityType.targetProperty (in this case: Employee.ClientID)
ParameterExpression numParam = Expression.Parameter(entityProperty, targetProperty.Substring(0, 3));
ConstantExpression equalTarget = Expression.Constant(idToDelete, idToDelete.GetType());
BinaryExpression intEqualsID = Expression.Equal(numParam, equalTarget);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
intEqualsID,
new ParameterExpression[] { numParam });
//(3)I want to create query1 fully dynamic, so defining Employee or an other type and its property at run time
WhereClause = lambda1.Compile();
IQueryable<Employee> employees = domainService.GetEmployees();
var query1 = employees.Where<Employee>(C => WhereClause.Invoke(C.ClientID)).Expression;
//(4)create the operand body {value(ASP.test_aspx).WhereClause.Invoke(E.ClientID)}
var operandbodyMethod = WhereClause.GetType().GetMethod("Invoke");
var operandbodyType = typeof(System.Boolean);
var operandbodyArgs1Expression = Expression.Parameter(entityType, entityType.Name.Substring(0, 1));
var operandbodyArgs1 = Expression.MakeMemberAccess(operandbodyArgs1Expression, entityType.GetMember(targetProperty)[0]);
var operandBodyObjectExp = Expression.Constant(this, this.GetType());
var operandbodyObject = Expression.MakeMemberAccess(operandBodyObjectExp, this.GetType().GetMember("WhereClause")[0]);
//(5)create the operand {E => value(ASP.test_aspx).WhereClause.Invoke(E.ClientID)}
var operandbody = Expression.Call(operandbodyObject, operandbodyMethod, operandbodyArgs1);
var operandParameter = Expression.Parameter(entityType, entityType.Name.Substring(0, 1));
var operandType = typeof(Func<,>).MakeGenericType(entityType, typeof(System.Boolean));
//(6)
var operand = Expression.Lambda(operandType, operandbody, new ParameterExpression[] { operandParameter });
var expressionType = typeof(Expression<>).MakeGenericType(operandType);
var completeWhereExpression = Expression.MakeUnary(ExpressionType.Quote, operand, expressionType);
//(7)the line below does not work
var query2 = employees.Where<Employee>(completeWhereExpression).Expression;
Thank you very much for reading my question!
If you have questions about my question, please ask them:)
This is quite hard to look at in isolation, but the first thing that occurs is that Compile looks out of place for IQueryable - that will rarely work (LINQ-to-Objects being the exception).
An equivalent to WhereClause.Invoke(C.ClientID) is to use Expression.Invoke to call a sub-expression, but even this is flakey: LINQ-to-SQL will support it, EF (in 3.5 at least) doesn't (maybe "didn't"; I haven't re-checked in 4.0). Ultimately, it would be more robust to create lambda1 as an Expression<Func<Employee,bool>> if possible:
ParameterExpression empParam = Expression.Parameter(typeof(Employee),"emp");
ConstantExpression equalTarget = Expression.Constant(idToDelete, idToDelete.GetType());
BinaryExpression intEqualsID = Expression.Equal(
Expression.PropertyOrField(empParam, targetProperty), equalTarget);
Expression<Func<Exmployee, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
intEqualsID,
empParam);
Then pass this to Where:
var query1 = employees.Where(lambda1);