Linq expression, get parameters? - c#

I got a linq expression,
internal T Execute<T>(System.Linq.Expressions.Expression<Func<int, string, T>> expr)
{
var paramInt = ??;
var paramString = ??;
}
I call this method using this:
Expression<Func<int, string, Guid>> myExpression2 = (a,b) => Callmethod(a, b, 5);
Execute<Guid>(myExpression2);
How can i get the parameters from the expression in my execute method ?
-- To clarify --
I want to get the values from the parameters so that i do some calculations with them.

Well, you can get the parameters using the Parameters property.
var parameters = expr.Parameters;
However, each of those will be a ParameterExpression. That will give you the parameter name and the type, but it's not clear what you want to do with them.

Related

Expression Func with two input parameters for generic method

I want to integrate this expression
Expression<Func<Customer, string, bool>> paramCompareFunc = (cust, name) => cust.Company == name;
For this
private bool IsUnique<Entity>(DbSet<Entity> set,
string param,
Expression<Func<Entity, string, bool>> paramCompareFunc)
where Entity : class
{
var query = set.Where(paramCompareFunc); // how I can pass param to expression?
// var query = set.Where(paramCompareFunc(param)); // error here
...
How I can pass the second parameter to the expression?
I want to define different compare expressions for different entities (they don't have any same name field) and to have a possibility to pass this expression into my generic function.
The "easy" way is by changing your api to use a factory method to build the Expression you actually need;
Expression<Func<Customer, bool>> GetCompareFunc(string name) => (cust) => cust.Company == name;
While you could use ReplacingExpressionVisitor to swap the name parameter with a constant, that would have a negative impact on performance.

C# Is there a way to resolve lambda expression`s delegate type in code

My goal is to support sorting in an application and expose it via REST API that would accept the parameter as a string.
Current implementation is along the lines of this:
GetUsers (Expression<Func<User, int>> sortExpression) {
// Retrieve users ...
return users.orderBy(sortExpression);
}
Usage example:
var users = GetUsers(u => u.Id);
the Expression<Func<User, int>> sortExpression is widely used in our repository and changing it would be difficult.
What I'd like to do is to be able to swap the u => u.Id with something that is generated during run-time.
Something that I have so far is:
// sortBy is retrieved from the calling method.
var type = typeof(User).GetProperties().FirstOrDefault(x => x.Name == sortBy).GetType();
var sortExpression = Expression.Property(Expression.Parameter(typeof(User)), sortBy);
var parameter = Expression.Parameter(typeof(User));
var expressionBody = Expression.Lambda(typeof(Func<User, int>), sortExpression, parameter);
var users = GetUsers(expressionBody)
I can see at run-time that this does create an expression that fits my needs, but the error is Argument 5: cannot convert from 'LambdaExpression' to 'Expression<System.Func<User, int>>' even though the body of the expression is supposed to be set by typeof(Func<User, int>)
I've figured out what I've been doing wrong.
First: Create the expression body using generic method
// Generic Method, return type Expression<Func<User, int>>
Expression.Lambda<Func<User, int>>(sortExpression, parameter);
Instead of passing the typeof(Func<User, int>) parameter.
// Non-generic. Return type LambdaExpression
Expression.Lambda(typeof(Func<User, int>), sortExpression, parameter);
Second:
I wasn't binding the parameter properly, which made it so that the expression was accessing property of a discarded parameter that wasn't provided to the expression.
// I'm creating an expression to access the property of a newly created parameter.
var sortExpression = Expression.Property(Expression.Parameter(typeof(User)), sortBy);
var parameter = Expression.Parameter(typeof(User));
var expressionBody = Expression.Lambda<Func<User, int>>(sortExpression, parameter);
//Above causes an unbinded variable exception since there are two parameters, one of which is not passed/bound.
//Should be:
var parameter = Expression.Parameter(typeof(User));
var sortExpression = Expression.Property(parameter, sortBy);

Generic method that accepts generic list of Expression<Func>

I have declared multiple of these variables, but how can I then put them into a generic list?
Expression<Func<poco, string>> fieldToUpdate1 = x => x.Name;
Expression<Func<poco, bool>> fieldToUpdate2 = x => x.Id;
Currently I can only specify one type for the generic list.
So I can either get a List<string> or List<bool>. But not both. I want to be able to have a generic list that accepts both so I can pass that list as a parameter.
Use case:
The use case I am trying to do is create a generic wrapper for the Mongo method updateOne. With the below signature. I want to create a generic wrapper that will accept two parameters. I can use these parameters to call the actual mongo implementation. Something like this:
GenericWrapper(Expression<Func<TDocument, bool>> filter, List<(Expression<Func<TDocument, TField>> expression, TField actual value)>)
The problem is that TField can only be one type. So I can only do this:
Expression<Func<Student, string>> fieldToUpdate1 = x => x.name;
Expression<Func<Student, int>> fieldToUpdate2 = x => x.testScore;
var expressions = new List<(Expression<Func<Student, int>> expression, int value)>();
var item1 = (expression: fieldToUpdate2, value: 4);
var item2 = (expression: fieldToUpdate1, value: "test");
expressions.Add(item1);
//I can't add item2 since its of a different type. I can only pass a list of the same type. And my generic wrapper function will only accept a list of one type
http://api.mongodb.com/csharp/current/html/M_MongoDB_Driver_IMongoCollectionExtensions_UpdateOne__1.htm
public static UpdateResult UpdateOne<TDocument>(
this IMongoCollection<TDocument> collection,
Expression<Func<TDocument, bool>> filter,
UpdateDefinition<TDocument> update,
UpdateOptions options = null,
CancellationToken cancellationToken = null
)
Any ideas on how to make this generic wrapper?
Since Expression<T> inherits from Expression you can put it into a List<Expression>.
List<Expression> expressions = new List<Expression>();
expressions.Add(fieldToUpdate1);
expressions.Add(fieldToUpdate2);
You can use object as the return value:
Expression<Func<poco, object>> fieldToUpdate1 = x => x.Name;
Expression<Func<poco, object>> fieldToUpdate2 = x => x.Id;
List<Expression<Func<poco, object>>> testList = new List<Expression<Func<poco, object>>>();
testList.Add(fieldToUpdate1);
testList.Add(fieldToUpdate2);
Anyway, the general design seems a bit strange, since at the end, you have to cast at least the results.

Create an expression tree that calls an expression tree

I have a property on a class
Expression<Func<Product, int>> predicate;
that will be assigned to different expressions throughout the application.
In a method called GetProducts I would like to retrieve Products from the DB using Entity Framework, using this predicate. I will then have a variable called myInt that I would like to use as the int parameter, that will then be assigned a value.
So I tried
dbContext.Products.Where(p => predicate(p, myInt))
but I got Non-invocable member 'predicate' cannot be used like a method. error.
It looks like I need to do some expression tree manipulation, to create a new expression tree, with myInt baked in it. How can I do this?
Thank you for your help.
You defined predicate as a Type but you are using it as a method.
You can define a predicate in the following way:
private bool predicate(Product product, int myInt)
{
//put your logic here
return true;
}
You can also use lambda expressions:
product => product.SomeValue > 5
Edit:
Expression<Func<Product, int>> predicate = (product,val) => product.SomeValue > val;
var filtered = dbContext.Products.Where(predicate);
Avoid using types when naming a parameters (i.e don't name an integer MyInt)
OK, got it.
ParameterExpression prm = Expression.Parameter(typeof(Product));
InvocationExpression inv = Expression.Invoke(predicate, prm, Expression.Constant(myInt));
var lambda = (Expression<Func<Product,bool>>) Expression.Lambda(inv, prm);
dbContext.Products.Where(lambda);
The key is Expression.Invoke that can invoke the predicate, using supplied parameters.
EDIT - After trying it, this only works with linq2sql - with Entity Framework it can't translate InvocationExpression to SQL. Instead, I used LinqKit, as follows:
dbContext.Products.AsExpandable().Where(p => predicate.Invoke(p, myInt))

How to Transform a LINQ Expression when you do not have one of the parameters when you define it

I'm trying to build more generic query functionality into my application. What I'd like to do is define objects which given an predicate expression can apply that to an iqueryable with a value that will be passed in later.
I believe the code below should demonstrate what I'm trying to do well enough to understand the problem. Please let me know if you'd like more details!
Thanks!
//in practice the value of this would be set in object constructor likely
private Expression<Func<Contact, string, bool>> FilterDefinition = (c, val) => c.CompanyName.Contains(val);
//this needs to filter the contacts using the FilterDefinition and the filterValue. Filterval needs to become the string parameter
private IQueryable<Contact> ApplyFilter(IQueryable<Contact> contacts, string filterValue)
{
//this method is what I do know know how to contruct.
// I need to take the FilterDefinition expression and create a new expression that would be the result if 'filtervalue' had been passed into it when it was created.
//ie the result would be (if 'mycompany' was the value of filterValue) an expression of
// c => c.CompanyName.Contains("mycompany")
Expression<Func<Contact, bool>> usableFilter = InjectParametersIntoCriteria(FilterDefinition, "SomeCompanyName");
//which I could use the results of to filter my full results.
return contacts.Where(usableFilter);
}
Are you looking for something like this?
private Func<string, Expression<Func<Contact, bool>>> FilterDefinition =
val => c => c.CompanyName.Contains(val);
private IQueryable<Contact> ApplyFilter(
IQueryable<Contact> contacts, string filterValue)
{
Expression<Func<Contact, bool>> usableFilter = FilterDefinition(filterValue);
return contacts.Where(usableFilter);
}
See: Currying
Place the following code in your ApplyFilter body:
var f = FilterDefinition.Compile();
return contacts.Where(x => f(x, filterValue));

Categories

Resources