Modifying Expression<Func<T, bool>> - c#

I haven't worked with expressions that much so I can't say if my intention makes any sense at all. I have looked around the interwebs and SO to no avail.
Say I have a method like so
public async Task<T> GetFirstWhereAsync(Expression<Func<T, bool>> expression)
{
// this would be a call to 3rd party dependency
return await SomeDataProvider
.Connection
.Table<T>()
.Where(expression)
.FirstOrDefaultAsync();
}
And from my-other-code I could be calling this e.g.
private async Task<User> GetUser(Credentials credentials)
{
return await SomeDataSource
.GetFirstWhereAsync(u => u.UserName.Equals(credentials.UserName));
}
So I'd be receiving first User from my SomeDataProvider that matches the given expression.
My actual question is how would I go about modifying GetFirstWhereAsync so that it would apply some SecretSauce to any expression passed to it? I could do this in caller(s) but that would be ugly and not much fun.
So if I pass in expressions like
u => u.UserName.Equals(credentials.UserName);
p => p.productId == 1;
I'd like these to be modified to
u => u.UserName.Equals(SecretSauce.Apply(credentials.UserName));
p => p.productId == SecrectSauce.Apply(1);

You can modify the expression inside method, but it's a bit complicated and you will need to do it case-by-case basis.
Example below will deal with modification from x => x.Id == 1 to x => x.Id == SecretSauce.Apply(1)
Class
class User
{
public int Id { get; set; }
public string Name { get; set;}
public override string ToString()
{
return $"{Id}: {Name}";
}
}
Sauce
class SquareSauce
{
public static int Apply(int input)
{
// square the number
return input * input;
}
}
Data
User[] user = new[]
{
new User{Id = 1, Name = "One"},
new User{Id = 4, Name = "Four"},
new User{Id = 9, Name = "Nine"}
};
Method
User GetFirstWhere(Expression<Func<User, bool>> predicate)
{
//get expression body
var body = predicate.Body;
//get if body is logical binary (a == b)
if (body.NodeType == ExpressionType.Equal)
{
var b2 = ((BinaryExpression)body);
var rightOp = b2.Right;
// Sauce you want to apply
var methInfo = typeof(SquareSauce).GetMethod("Apply");
// Apply sauce to the right operand
var sauceExpr = Expression.Call(methInfo, rightOp);
// reconstruct equals expression with right operand replaced
// with "sauced" one
body = Expression.Equal(b2.Left, sauceExpr);
// reconstruct lambda expression with new body
predicate = Expression.Lambda<Func<User, bool>>(body, predicate.Parameters);
}
/*
deals with more expression type here using else if
*/
else
{
throw new ArgumentException("predicate invalid");
}
return user
.AsQueryable()
.Where(predicate)
.FirstOrDefault();
}
Usage
Console.WriteLine(GetFirstWhere(x => x.Id == 2).ToString());
The method will turn x => x.Id == 2 to x => x.Id == SquareSauce.Apply(2) and will produce:
4: Four

Related

Using filters in linq expression tree

My idea is that I expose an api controller ​/api​/Events​/{clientId}​/{projectId}/{eventTypeId}
these tree parameters will be acting like filters in querying my db.Events table.
If I pass ClientId, and not pass other two filters it should just do filtering by ClientId.
I tried with this method:
public async Task<IEnumerable<Event>> Handle(GetEventsByClientIdAndProjectIdQuery query, CancellationToken cancellationToken)
{
Expression<Func<Event, int?>> clientId = (s => query.ClientId);
Expression<Func<Event, int?>> projectId = (s => query.ProjectId);
Expression<Func<Event, int?>> eventTypeId = (s => query.EventTypeId);
if (query.ProjectId.HasValue)
{
projectId = (p => p.ProjectId.Equals(query.ClientId)); //error: cannot implicitly convert type bool to int?
}
if (query.EventTypeId.HasValue)
{
eventTypeId = (e => e.EventTypeId == query.EventTypeId.Value);
}
var evts = await _context.Events.Where(clientId) //error: Argument 2: cannot convert from 'System.Linq.Expressions.Expression<System.Func<UNW.Domain.Entities.Event, int?>>' to 'System.Func<UNW.Domain.Entities.Event, bool>'
.Where(projectId)
.Where(eventTypeId)
.ToListAsync();
if (evts == null)
return null;
return evts.AsReadOnly();
}
and my GetEventsByClientIdAndProjectIdQuery model:
public int? ProjectId { get; set; }
public int? ClientId { get; set; }
public int? EventTypeId { get; set; }
What I am missing?
You can made it simpler
public async Task<IEnumerable<Event>> Handle(GetEventsByClientIdAndProjectIdQuery query,
CancellationToken cancellationToken)
{
var dbQuery = _context.Events;
if (query.ProjectId.HasValue)
{
dbQuery = dbQuery.Where(p => p.ProjectId.Equals(query.ClientId));
}
if (query.EventTypeId.HasValue)
{
dbQuery = dbQuery.Where(e => e.EventTypeId == query.EventTypeId.Value);
}
//same goes for projectID which is missing from your question
var evts = await dbQuery.ToListAsync();
//evts will nerver be null, you might want do something for evts.Count==0
//but it is fine to return an empty list
return evts.AsReadOnly();
}
The most concise solution I can think of
public async Task<IEnumerable<Event>> Handle(GetEventsByClientIdAndProjectIdQuery query, CancellationToken cancellationToken)
=> await _context.Events
.Where(evt => evt.ClientId.Equals(query.ClientId))
.Where(evt => query.ProjectId.HasValue ? evt.ProjectId.Equals(query.ProjectId.Value) : true)
.Where(evt => query.EventTypeId.HasValue ? evt.EventTypeId.Equals(query.EventTypeId.Value) : true)
.ToListAsync(cancellationToken)
.AsReadOnly();
If the filter is provided
Then use it
Otherwise do not filter out the element
I like and am a fan of the simplicity of #Magnetron's answer, but to build off of your existing code:
//error: cannot implicitly convert type bool to int?
Issue 1: The signatures for clientId, projectId and eventTypeId are all set up to return a nullable int (Func<Event, int?), but .Equals() returns a boolean value.
Assuming I'm understanding what you want to accomplish, you can try the below updates:
// 1. Change the return values from int? to boolean.
// 2. Go ahead and set your expression to return Events where the ClientId is equal
// to the ClientId passed in with your query parameter
Expression<Func<Event, bool>> clientId =
(s => s.ClientId.Equals(query.ClientId));
// 3. Similar to the above with ClientId, but we also include the guard clauses for the optional parameters (ProjectId and EventTypeId)
Expression<Func<Event, bool>> projectId =
(s => (!query.ProjectId.HasValue || query.ProjectId == 0) || s.ID.Equals(query.ProjectId));
Expression<Func<Event, bool>> eventTypeId =
(s => (!query.EventTypeId.HasValue || query.EventTypeId == 0) || s.ID.Equals(query.EventTypeId));
// (Issue 2 with clientId resolved by the updates made to resolve Issue 1)
var evts = await _context.Events.Where(clientId)
.Where(projectId)
.Where(eventTypeId)
.ToListAsync();
if (evts == null)
return null;
return evts.AsReadOnly();

every Parameter object property that is not null, to be added to expression predicate as a condition

I am looking for a way to dynamically build an expression, based upon the method parameters.
This is the code snippet from my service method, where I would like to build a predicate expression.
public async Task<Account> GetCustomerAccountsAsync(Parameters params)
{
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && ... );
...
}
GetWhereAsync is a method from the generic repository that looks like:
public async Task<IEnumerable<TEntity>> GetWhereAsync(Expression<Func<TEntity, bool>> predicate)
{
return await Context.Set<TEntity>().Where(predicate).ToListAsync();
}
And Parameters class:
public class Parameters
{
public string CustomerId { get; set; }
public string AccountId { get; set; }
public string ProductId { get; set; }
public string CurrencyId { get; set; }
}
What I would like to implement, is that every Parameter object property that is not null,
to be added to expression predicate as a condition.
For example, if CustomerId and AccountId have some values, I would like to dynamically build predicate expression
that would have functionality same as the following predicate:
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && a.AccountId == params.AccountId);
Appreciate any help.
You don't need to use Expressions to build something dynamically here. You can do something like this:
_unitOfWork.Accounts.Where(a =>
(params.CustomerId == null || a.CustomerId == params.CustomerId) &&
(params.AccountId == null || a.AccountId == params.AccountId) &&
(params.ProductId == null || a.ProductId == params.ProductId) &&
(params.CurrencyId == null || a.CurrencyId == params.CurrencyId)
);
This is how I've done queries before for a search form with multiple optional search parameters.
I'm currently working a lot with Expressions so I think I can help you.
I just built a custom code for you.
The code accepts that you add Properties to your filtered class (Account) without having to change the filter building code.
The code filters string Properties and use it to create the Predicate.
public Func<Account, bool> GetPredicate(Parameters parameters)
{
var stringProperties = typeof(Parameters)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(string));
var parameterExpression = Expression.Parameter(typeof(Account));
var notNullPropertyNameToValue = new Dictionary<string, string>();
BinaryExpression conditionExpression = null;
foreach (var stringProperty in stringProperties)
{
var propertyValue = (string)stringProperty.GetValue(parameters);
if (propertyValue != null)
{
var propertyAccessExpression = Expression.PropertyOrField(parameterExpression, stringProperty.Name);
var propertyValueExpression = Expression.Constant(propertyValue, typeof(string));
var propertyTestExpression = Expression.Equal(propertyAccessExpression, propertyValueExpression);
if (conditionExpression == null)
{
conditionExpression = propertyTestExpression;
}
else
{
conditionExpression = Expression.And(conditionExpression, propertyTestExpression);
}
}
}
//Just return a function that includes all members if no parameter is defined.
if (conditionExpression == null)
{
return (x) => true;
}
var lambdaExpression = Expression.Lambda<Func<Account, bool>>(conditionExpression, parameterExpression);
return lambdaExpression.Compile();
}
It returns a typed predicate that you can use in Linq for example.
See this example :
void Main()
{
var customers = new List<Account>()
{
new Account()
{
CustomerId = "1",
},
new Account()
{
CustomerId = "2",
}
};
var predicate = GetPredicate(new Parameters() { CustomerId = "1" });
customers.Where(predicate);
}
If any help is needed, feel free to ask !

How to set the lambda if the properties won't be known until runtime?

I'm trying to make a generic method that I can use anywhere within my application.
Here is my method:
public T GetEntry(ObjectId id)
{
IMongoCollection<T> collections = db.GetCollection<T>(database);
var getObj = collections.Find( ).FirstOrDefault(); // something like x => x.id == id
return getObj;
}
Find takes a lambda expression that will specify the constraints but I can't set it because the properties won't be known until runtime. How do I set it up?
Do you want to change the search expression at the caller? In that case it's probably easiest to just pass in the expression from the caller. Something like this:
public T GetEntry<T>(ObjectId id, Expression<Func<T, bool>> predicate)
{
IMongoCollection<T> collections = db.GetCollection<T>(database);
var getObj = collections.Find(predicate).FirstOrDefault(); // Call passed in predicate
return getObj;
}
Then when you call the function you can do something like:
var person = GetEntry<Person>(id, person => person.Id == id);
You can use Interface for this solution
public Interface IEntity
{
int Id {set ;get}
}
class EntryManagement<T> where T : IEntity
{
public T GetEntry(ObjectId id)
{
IMongoCollection<T> collections = db.GetCollection<T>(database);
var getObj = collections.Find(x => x.Id == id).FirstOrDefault();
return getObj;
}
}
or you can create your lambda expression dynamically at runtime
public T GetEntry(ObjectId id)
{
IMongoCollection<T> collections = db.GetCollection<T>(database);
var parameterExpression = Expression.Parameter(typeof(T), "object");
var propertyOrFieldExpression = Expression.PropertyOrField(parameterExpression, "Id");
var equalityExpression = Expression.Equal(propertyOrFieldExpression, Expression.Constant(id, typeof(int)));
var lambdaExpression = Expression.Lambda<Func<T, bool>>(equalityExpression, parameterExpression);
var getObj = collections.Find(lambdaExpression).FirstOrDefault();
return getObj;
}

How to dynamically create a lambda expression

Let's suppose I have the following class:
public class Show
{
public string Language { get; set; }
public string Name { get; set; }
}
Based on this information, my goal is to create a lambda expression like this:
g => g.Language == lang && g.Name == name
lang and name are local variables I would like to add as constant values when creating the expression.
As you can see, the compiled function would be of type Func<Genre, bool>
To help you understand more clearly, I would like to achieve something similar to this:
string lang = "en";
string name = "comedy";
Genre genre = new Genre { Language = "en", Name = "comedy" };
Expression<Func<Genre, bool>> expression = CreateExpression(genre, lang, name);
// expression = (g => g.Language == "en" && g.Name == "comedy")
I am aware of the existence of expression trees but I'm pretty much new to this topic so I don't even know how to start.
Can this problem be solved? How can I create such expression dynamically?
public Expression<Func<TValue, bool>> CreateExpression<TValue, TCompare>(TValue value, TCompare compare)
{
var pv = Expression.Parameter(typeof(TValue), "data");
var compareProps = typeof(TCompare).GetProperties();
// First statement of the expression
Expression exp = Expression.Constant(true);
foreach (var prop in typeof(TValue).GetProperties())
{
// Check if the compare type has the same property
if (!compareProps.Any(i => i.Name == prop.Name))
continue;
// Build the expression: value.PropertyA == "A"
// which "A" come from compare.PropertyA
var eq = Expression.Equal(
Expression.Property(pv, prop.Name),
Expression.Constant(compareProps
.Single(i => i.Name == prop.Name)
.GetValue(compare)));
// Append with the first (previous) statement
exp = Expression.AndAlso(exp, eq);
}
return Expression.Lambda<Func<TValue, bool>>(exp, pv);
}
Usage:
var value = new { Lang = "en", Name = "comedy"};
// The compareValue should have the same property name as the value,
// or the expression will just ignore the property
var compareValue = new { Lang = "en", Name = "comedy", Other = "xyz" };
// The create expression content is
// {data => ((True AndAlso (data.Lang == "en")) AndAlso (data.Name == "comedy"))}
bool isMatch = CreateExpression(value, compareValue).Compile()(value); // true
You can use interfaces in order to create a Func against the interface who has defined the necessary properties.
Example:
interface IExpressionable {
string Language { get;set; }
string Name { get;set; }
}
class Genre : IExpressionable {
string Language {get;set;}
string Name {get;set;}
}
Genre genre = new Genre();
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name);
expression = (g => g.Language == "en" && g.Name == "comedy")
An alternative implementation of this concept can be had by providing a GetLanguage and GetName method on your interface, which would force subscribers to implement the underlying methods in order to return a "Language" and "Name" based on their own internal methods.
interface IExpressionable {
string GetExpressionOne();
string GetExpressionTwo();
}
class Genre : IExpressionable {
string Language {get;set;}
string Name {get;set;}
public string GetExpressionOne() {
return Language;
}
public string GetExpressionOne() {
return Name;
}
}
class SomethingElse {
string Orange {get;set;}
string BananaPeel {get;set;}
public string GetExpressionOne() {
return Orange;
}
public string GetExpressionOne() {
return BananaPeel;
}
}
Genre genre = new Genre();
SomethingElse else = new SomethingElse();
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name);
Expression<Func<IExpressionable, bool>> expression2 = CreateExpression(else, lang, name);
expression = (g => g.GetExpressionOne() == "en" && g.GetExpressionTwo() == "comedy");
Edit from your comment above: #kaveman I only have the key values, but I can fetch the key properties via reflection using some custom attributes that I defined. In this example, Language and Name would be decorated with an attribute that defines them as key properties
My answer is to avoid this thought completely. Create an interface that defines the methods you need to perform whatever expression you are looking for, and have the entities inherit from it. You could have your methods on the interface be "GetKeyOne" and "GetKeyTwo". Then your expression doesn't have to be dynamic, you just need to define what the expression does when it is interacting with KeyOne and KeyTwo, which is defined in each implementor.
Assuming you have attributes on each of the properties of each of the entities that you are interested in dealing with here (see comments), and you have the type parameter (call it T) a possible signature for CreateExpression is:
Expression<Func<T, bool>> CreateExpression(T entity, IOrderedDictionary<string, string> propertyTests);
In this way, your CreateExpression works with the known generic type, and the caller can specify what tests to make against the entity's properties. Here we assume the key is the property name to reflect on (ensure it has the known attribute) while the value is the required value of said property. IOrderedDictionary is a way to enforce short-circuit of the && tests, if that's your thing.
You can add additional constraints on T (e.g. T must implement some interface) via generic type constraints using where
See also Check if property has attribute
And Reflection - get attribute name and value on property
This will do it, and should make it fairly user-friendly to build
private Expression<Func<Genre,bool>> CreateExpression(params Expression<Func<Object>>[] selectors)
{
//We are working on a Genre type, and make a parameter of it
//Query so far looks like g =>
var param = Expression.Parameter(typeof(Genre),"g");
//Set the base expression to make it easy to build
//Query so far looks like g => true
Expression expression = Expression.Constant(true);
foreach(var selector in selectors) {
//Find out the name of the variable was passed
var selectorname = TestExtension.nameof(selector);
//Get the value
var selectorValue = selector.Compile()();
//Create an accessor to the genre (g.Language for example)
var accessMethod = Expression.PropertyOrField(param, selectorname);
//Check if it equals the value (g.Language == "en")
var equalExpr = Expression.Equal(accessMethod, Expression.Constant(selectorValue));
//Make it an And expression
//Query so far looks like g => true && g.Language == "en"
expression = Expression.AndAlso(expression, equalExpr);
//Second pass through the loop would build:
//g => true && g.Language == "en" && g.Name == "comedy"
}
//Turn it into a lambda func and cast it
var result = Expression.Lambda(expression, param) as Expression<Func<Genre, bool>>;
return result;
}
public class Genre
{
public string Language { get; set; }
public string Name { get; set; }
}
//Taken from my answer at http://stackoverflow.com/a/31262225/563532
public static class TestExtension
{
public static String nameof<T>(Expression<Func<T>> accessor)
{
return nameof(accessor.Body);
}
public static String nameof<T, TT>(this T obj, Expression<Func<T, TT>> propertyAccessor)
{
return nameof(propertyAccessor.Body);
}
private static String nameof(Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = expression as MemberExpression;
if (memberExpression == null)
return null;
return memberExpression.Member.Name;
}
return null;
}
}
Then you use it like this:
string language = "en";
string name = "comedy";
var genre = new Genre { Language = "en", Name="comedy" };
var query = CreateExpression(() => language, () => name);
Note that the variables must match the property names. Otherwise, you will need some kind of mapping [lang => language] etc.
To evaluate it:
var matches = query.Compile()(genre);
Or, you can pass it into EF for example:
dtx.Genre.Where(query);

Append to an expression

I followed this thread: link text
Jason gives an example:
public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}
and its usage as such:
Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}
I have a orders table and i followed the above example, changing column names, and i get the similar error that the post creator had
The binary operator AndAlso is not defined for the types 'System.Func2[Models.Order,System.Boolean]' and 'System.Func2[Models.Order,System.Boolean]'.
Anyone have any thoughts on what I am missing?
UPDATED:
Eric, I further followed what the user of the previous post was asking, here link text
The user has this
Expression<Func<Client, bool>> clientWhere = c => true;
Expression<Func<Order, bool>> orderWhere = o => true;
Expression<Func<Product, bool>> productWhere = p => true;
if (filterByClient)
{
clientWhere = c => c.ClientID == searchForClientID;
}
Now if he were to have various conditions in filterByClient, say he either has clientid and/or some other column name, how would one build the clientWhere expression?
You're attempting to build an expression tree that represents this:
c => true && c.ClientFName == searchForClientFName
You are actually building an expression tree that represents this:
c => c=> true && c => c.ClientFName == searchForClientFName
which makes no sense at all.
Now, you might naively think that this will work:
public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
// NOTICE: Combining BODIES:
return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters);
}
That would produce in your case something representing
c => true && c.ClientFName == searchForClientFName
Which looks right. But in fact this is fragile. Suppose you had
... d => d.City == "London" ...
... c => c.ClientName == "Fred Smith" ...
and you used this method to combine them. You'd get an object representing
c => d.City == "London" && c.ClientName == "Fred Smith"
What the heck is d doing in there?
Furthermore, parameters are matched by object identity, not by parameter name. If you do this
... c => c.City == "London" ...
... c => c.ClientName == "Fred Smith" ...
and combine them into
c => c.City == "London" && c.ClientName == "Fred Smith"
you're in the same boat; the "c" in "c.City" is a different c than the other two.
What you actually need to do is make a third parameter object, substitute it in the bodies of both lambdas for every occurence of their parameters, and then build up a new lambda expression tree from the resulting substituted bodies.
You can build a substitution engine by writing a visitor that passes over the expression tree body, rewriting it as it goes.
It was difficult for me to understand hvd's answer so I created some code to explain it in a different way. hvd should get the credit for suggesting the ExpressionVisitor. I just couldn't understand the example in the context of Linq to X type input functions I was using.
I hope this helps somebody else coming to the question from that perspective.
Also, I created the combining code as extension methods to make it a little easier to use.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy");
Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" }));
Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" }));
Console.ReadLine();
}
public class FullName
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2)
{
return func1.CombineWithAndAlso(func2).Compile();
}
}
public static class CombineExpressions
{
public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
{
return Expression.Lambda<Func<TInput, bool>>(
Expression.AndAlso(
func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
func1.Parameters);
}
public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
{
return Expression.Lambda<Func<TInput, bool>>(
Expression.AndAlso(
func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
func1.Parameters);
}
private class ExpressionParameterReplacer : ExpressionVisitor
{
public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
{
ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
ParameterReplacements.Add(fromParameters[i], toParameters[i]);
}
private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; }
protected override Expression VisitParameter(ParameterExpression node)
{
ParameterExpression replacement;
if (ParameterReplacements.TryGetValue(node, out replacement))
node = replacement;
return base.VisitParameter(node);
}
}
}
}
If you need it i created a small fluent library to create lambda functions on the fly without directly coping with System.Linq.Expressions. And it can easily handle the kind of situation. Just to give an example:
static void Main(string[] args)
{
var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName);
var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName);
Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy");
var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"};
Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck));
toCheck = new FullName {FirstName = "Cat", LastName = "Boy"};
Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck));
Console.ReadLine();
}
The GetComparer methods seek for the property passed as expression and find ho to get its value, then it builds a new Expression that will handle the comparaison.
At the end the two functions are evaluated calling the "combined" function.
If you need more verifications you could use an array and iterate on it inside the "combined lambda"
The code and documentation for the library are here: Kendar Expression Builder
While the nuget package is here: Nuget Expression Builder
I tried to implement this kind of stuff. Took me a day to find out.
My solution is based on filter in a loop based on a Array of predicate.
As a note, it s totally Generic and based Reflection because the only information about class and field are String.
To make it simple, i call directly the Model class but in a project you should go by a controler who is calling the Model.
So here we go :
The Model part where T is a Generic in the class
public class DALXmlRepository<T> where T : class
{
public T GetItem(Array predicate)
{
IQueryable<T> QueryList = null;
QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
for (int i = 1; i < predicate.GetLength(0); i++)
{
QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
}
if (QueryList.FirstOrDefault() == null)
throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
return QueryList.FirstOrDefault();
}
}
Now the LambdaExpression Builder, it's a base one(with String type or something else) , you can improve it with more functionnality :
private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
{
LambdaExpression lambda = null;
Expression Criteria = null;
Random r = new Random();
ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());
if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
//Type du champ recherché
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(FieldValue, propType);
Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
}
else
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);
Criteria = Expression.Equal(left, right);
}
lambda = Expression.Lambda(Criteria, predParam);
return lambda;
}
Now the Calling function :
public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
{
//Get the type
Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
//Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });
//Building the string type Expression<func<T,bool>> to init the array
Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);
MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });
if (method == null)
throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);
int j = 0;
IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
criterias.Reset();
while (criterias.MoveNext())
{
if (!String.IsNullOrEmpty(criterias.Key.ToString()))
{
lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
}
else
{
throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
}
j++;
}
Object item = method.Invoke(DalInstance, new object[] { lambda });
}
The argument are :
String Entity : Entity class name.
XMLContext : it s the unit of work of the repository, argument i use to initialize the Model class
Hashtable FieldsNameToGet : Index/value of the list of the field i want to get back
Hashtable FieldFilter : the key/Value with FieldName/Content used to make the Lambda expression
Good Luck.

Categories