Combine two predicates of different type - c#

I have built a predicate by the Address model (Expression<Func<Address, bool>> addressPred). Now i want to combine this with a predicate of the Person model (Expression<Func<Person, bool>> personPred). Example of the models are:
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string StreetName { get; set; }
}
If I do something like
addressPred = addressPred.And(a => a.StreetName == "Foo bar");
and then combine it with the personPred, the combined predicate will have a statement equalent to
combinedPred = combinedPred.And(a => a.Address.StreetName == "Foo bar");
The combinedPredicate is of type Person.
How can I achive this?
Edit:
In reality the models are alot bigger and are shortened for simplicity's sake. What I ultimately want to achive is to build the Address predicate once, then build a predicate for Person with its own conditions and then combine it into a predicate where the Address-part of the Person predicate comes from the Address predicate (Address is a property of Person). The reason I want to do it this way is because Address may have alot of conditions, and I want to use it as a part of other predicates (Person, later also Company, Customer etc) before making a db-call (to Person, Company, Customer etc)
Also the combinedPred line of code was only to show what the equalent statement of the combinedPred would be like coming from Address predicate.

The situation is that the built Expression<Func<Address, bool>> addressPred has an address as ParameterExpression and it is requred to apply the expression to a MemberExpression. So I can suggest to replace the ParameterExpression with the MemberExpression like that:
var personParameter = Expression.Parameter(typeof(Person), "person");
var addressProperty = Expression.PropertyOrField(personParameter, "Address");
var combined = new ReplaceVisitor<Address>(addressProperty).Visit(addressPred.Body);
var result = Expression.Lambda<Func<Person, bool>>(combined, personParameter);
Where:
public class ReplaceVisitor<T> : ExpressionVisitor
{
private readonly MemberExpression _replacement;
public ReplaceVisitor(MemberExpression replacement)
{
_replacement = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type.IsAssignableFrom(typeof(T))
? _replacement : base.VisitParameter(node);
}
}

Related

Is there a way to map Func<T1, bool> to Func<T2, bool>?

So, I was wondering if it possible to do the next thing in c#:
I have a DB model - let's say it is Car:
public class Car {
public string Id {get;set;}
public string Name {get;set}
}
And a DbSet for this type in someDbContext:
public DbSet<Car> Cars {get;set;}
And also I have a CarDto
public class CarDto {
public string Id {get;set;}
public string Name {get;set}
}
And as result we get something like this:
var select = new Func<CarDto, bool>(car => car.Name == "BMW");
// And somehow use this expression for other type Car
someDbContext.Cars.Where(select);
Maybe there is an approach in which I could map these Funcs like this:
var newFunc = mapper.Map<Func<Car, bool>>(select);
Any thoughts?
If you just want to handle rewriting property accesses, you can use an ExpressionVisitor which looks a bit like this:
public class Program
{
public static void Main()
{
Expression<Func<Car, bool>> expr = x => x.Name == "BMW";
var replaced = ReplaceParameter<CarDto>(expr);
}
private static Expression<Func<T, bool>> ReplaceParameter<T>(LambdaExpression expr)
{
if (expr.Parameters.Count != 1)
throw new ArgumentException("Expected 1 parameter", nameof(expr));
var newParameter = Expression.Parameter(typeof(T), expr.Parameters[0].Name);
var visitor = new ParameterReplaceVisitor()
{
Target = expr.Parameters[0],
Replacement = newParameter,
};
var rewrittenBody = visitor.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(rewrittenBody, newParameter);
}
}
public class ParameterReplaceVisitor : ExpressionVisitor
{
public ParameterExpression Target { get; set; }
public ParameterExpression Replacement { get; set; }
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == this.Target)
{
// Try and find a property with the same name on the target type
var members = this.Replacement.Type.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Public | BindingFlags.Instance);
if (members.Length != 1)
{
throw new ArgumentException($"Unable to find a single member {node.Member.Name} of type {node.Member.MemberType} on {this.Target.Type}");
}
return Expression.MakeMemberAccess(this.Replacement, members[0]);
}
return base.VisitMember(node);
}
}
We need to deconstruct the LambdaExpression into its body and parameters. We need to create a new parameter which has the correct type, and replace all usages of the old parameter with the new one. This is where the visitor comes in: whenever it sees you access a member on the old parameter, it tries to find the corresponding member on the new parameter, and access that instead.
We then construct a new LambdaExpression, using the rewritten body and the new parameter.
You have a whole bunch of options:
Derive your Dto class from the context class. That way you can use polymorphism as normal.
Extract an interface and implement it in both your Dto and context classes. Same as above then, use polymorphism.
Use duck-typing. In C#, that's done with the dynamic keyword. You lose Intellisense and compile-time error checking, but your code will work.
Reflection. It's a lot of code, it's slow, it's practically a much worse version of #3, but you can cobble it together if you really try.
Something like Automapper will help you map your context to your Dto piece-wise, but it won't help you translate your lambda function filters.

Filtering but property and child entity property

I got a small problem with building dynamic expression tree for search logic. Creating an expression tree for entity own properties is working fine, but I've no idea how to add an expression which will filter by child entity properties.
Here is my Task entity:
public class Task: Entity
{
public TaskType Type { get; set; }
public TaskPriority Priority { get; set; }
public int ProjectId { get; set; }
public Project Project { get; set; }
}
And here is Project entity:
public class Project: Entity
{
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
And logic for building dynamic expression:
public Func<TaskItem, bool> Build(IList<Filter> filters)
{
ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
List<Filter> priorityFilter = FilterFilters(filters, "Priority");
List<Filter> typeFilter = FilterFilters(filters, "Type");
List<Filter> customerFilter = FilterFilters(filters, "CustomerId");
Expression expression = null;
// BuildExpression is a method which simply creates expression which is using Tasks properties (like Type or Priority)
expression = BuildExpression(param, priorityFilter, expression);
expression = BuildExpression(param, typeFilter, expression);
// This part need's to be reworked
ParameterExpression projectParam = Expression.Parameter(typeof(Project), "project");
Expression projectCustomerExpression = Expression.Equal(Expression.PropertyOrField(projectParam, "CustomerId"), Expression.Constant(customerFilter[0].Value));
Expression customerExpression = Expression.Equal(Expression.PropertyOrField(param, "Project"), projectCustomerExpression);
Expression finall = expression != null ? Expression.AndAlso(expression, projectCustomerExpression) : projectCustomerExpression;
// End of filtering by CutomerId
return Expression.Lambda<Func<TaskItem, bool>>(finall, param).Compile();
}
I've no idea how to filter by CustomerId. The part above code marked as This part need's to be reworked is probably wrong or at least the last part of it. The idea is to extend existing expression (this one build by BuildExpression method) with an expression which will filter by CustomerId.
I already lost some time on this, trying on my own and looking for answers but with no results.
Any help?
As you have provided a minimal code, how you are creating the actual expressions is unknown. So, I will try to provide a general recipe for this scenario.
If you want to filter a list of Task then you still need to use the same ParameterExpression of type Task, like you have already done:
ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
There is no need to create another ParameterExpression of type Project even if you want to filter on properties of Project. In stead you just need to reuse the former ParameterExpression. Note that if we build a static predicate like below, this is also the case that we also don't use a different parameter expression:
queryableTask.Where(t => t.Priority == TaskPriority.High && t.Project.CustomerId == 123);
Now to dynamically build filter on navigational (child) property, the key is to form the left expression (i.e. expression for navigational property) correctly.
Lets say our navigational property is in dot notation: Project.CustomerId. Then we can do something like this to create the left expression for property:
// We already have the following commented properties
// prop = "Project.CustomerId";
// ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
var leftExpr = prop.Split('.')
.Aggregate<string, MemberExpression>(null,
(acc, p) => acc == null
? Expression.Property(param, p)
: Expression.Property(acc, p));
And then you can do the rest like a normal property, such as creating the right expression and combining them with another expression defining the operator (Equal, Not Equal etc.).
Hope this helps.

Expression<Func<T1, T2, TResult>> and Sql IN

I want to use expression trees to make filters with entity framework.
So this is my types
public class Type1
{
public string Name { get; set; }
}
public class Type2
{
public IEnumerable<string> Names { get; set; }
}
and this is my specification
public Expression<Func<Entities.Type1, bool>> MyExpression(Type2 filter)
{
//something like where name in (name[0], name[1], ... name[n])
}
I need transform this in something like Sql where in.
How can I do it, and what's the best form?
How can I make Entity Framework understand my arbitrary expression in the way I want?
You can do it simply like this:
public Expression<Func<Type1, bool>> MyExpression(Type2 filter)
{
return x => filter.Names.Contains(x.Name);
}
You can try this:
public Expression<Func<Type1, bool>> MyExpression(Type2 filter)
{
Expression<Func<Type1, bool>> expression = t1 => filter.Names.Contains(t1.Name);
return expression;
}
In this post you can find good explanation why you can convert a lambda expression to an expression tree.

Generic Find Method on Repository using Mongo

I am trying to make a generic repository with a find operation that takes an expression. I have the following right now (I am using FastMapper for projecting from my entity objects to an external contract object):
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Project().To<T>().Where(predicate).ToList();
}
The problem is I get the following exception: "Where with predicate after a projection is not supported."
I can do the following, but this would severely poor performance wise because it would involve retrieving every record from the database before performing the filtering:
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Project().To<T>().ToList().AsQueryable().Where(predicate).ToList();
}
I am wondering if there is a way to convert the expression from a T to a Y object so that I could do the following instead (which I think would be the most performant because it then passes the filtering down to the database and only performs Project on the resultset):
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Where(predicate).Project().To<T>().ToList();
}
any help would be greatly appreciated. Thanks.
Update
So, using the information from this question (Question) I was able to get closer to what I was looking for. I am now able to do the following:
public override List<T> Find(Expression<Func<T, bool>> predicate)
{
var newPredicate = TransformPredicateLambda<T, Y>(predicate);
var collection = _database.GetCollection<Y>(_collectionName);
return collection.AsQueryable<Y>().Where(newPredicate).Project().To<T>().ToList();
}
The only thing I have left to resolve is retrieving the fastmapper mappings at this point (if property is null part):
protected override Expression VisitMember(MemberExpression node)
{
var dataContractType = node.Member.ReflectedType;
var activeRecordType = _typeConverter(dataContractType);
var property = activeRecordType.GetProperty(node.Member.Name);
if (property == null)
{
}
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
property
);
return converted;
}
essentially, some of my objects might look like this:
//object used throughout my code
public class Store
{
public string StoreId {get; set;}
public Account Account {get; set;}
...
}
//object used in access layer only
public class Store
{
public string StoreId {get; set;}
public string AccountId {get; set;}
...
}
And in my initialization script, I define a type adapter like so:
TypeAdapterConfig<Store, Models.Store>.NewConfig()
.MapFrom(a => a.Id, s => s.StoreId != null ? new ObjectId(s.StoreId) : new ObjectId())
.MapFrom(d => d.AccountId, s => s.Account.AccountId)
.IgnoreNullValues(true);
The reason you can't do a projection from Y to T prior to handing the query to MongoDB is that MongoDB doesn't know anything about T. We only know about Y (because that is the type of the collection). For instance:
class Person
{
[BsonElement("fn")]
public string FirstName { get; set; }
[BsonElement("ln")]
public string LastName { get; set; }
}
class PersonView
{
public string FullName { get; set; }
}
And your projector does something like this:
person => new PersonView { FullName = person.FirstName + person.LastName }
We don't have access to the code in the projector and have no idea that FullName is the concatenation of FirstName and LastName and therefore can't tell MongoDB to do this.
The LINQ support in the 1.x version of the driver cannot target the Aggregation Framework, which is the only place this would be legal, and only assuming your projector produced an Expression<Func<Person, PersonView>> and not a compiled Func<Person, PersonView>. However, the 2.x version of the driver will have better support for this, although it really depends on what FastMapper does underneath.
======
So, your current options are this:
Change the method signature to be Expression<Func<Y, bool>>. This would not only satisfy your needs, but also limit the documents you are projecting to only those that pass the filter.
If T inherits from Y, you could start with an OfType and not even need the projector: collection.AsQueryable<Y>().OfType<T>.Where(predicate).ToList();

Mapping DTO classes with Expression<Func> inside another Expression<Func>

I'm working with the Entity Framework Code First and trying to map from my entity classes to my DTO classes. But I'm having difficult figuring out how to write the Selector.
In this small example, I have created a Person class and a Address class.
In the DTO classes I have created a Selector, which maps from my Entity to my DTO, but is it not possible to use AddressDto.Selector inside the PersonDto.Selector?
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
}
Now I'm trying to map this to a DTO classes.
public class PersonDto
{
public static Expression<Func<Person, PersonDto>> Selector =
entity => new PersonDto
{
Id = entity.Id,
Name = entity.Name,
Address = ??? AddressDTO.Selector
};
public int Id { get; set; }
public string Name { get; set; }
public AddressDto Address { get; set; }
}
public class AddressDto
{
public static Expression<Func<Address, AddressDto>> Selector =
entity => new AddressDto
{
Id = entity.Id,
Street = entity.Street
};
public int Id { get; set; }
public string Street { get; set; }
}
I know that I could just write this inside PersonDto.Selector
Address = new AddressDto
{
Id = entity.Address.Id,
Street = entity.Address.Street
};
But I'm looking for a way to reuse the Selector from the AddressDto class. To keep the code clean and separate responsibility between classes.
So, we're going to need several helper methods here, but once we have them, things should be fairly simple.
We'll start out with this class that can replace all instance of one expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Then an extension method to make calling it easier:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
Next we'll write a compose extension method. This will take a lambda that computes an intermediate result, and then another lambda that computes a final result based on the intermediate result and returns a new lambda that takes what the initial lambda returns and returns the output of the final lambda. In effect it calls one function, and then calls another on the result of the first, but with expressions, not methods.
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Now we'll create a Combine method. This will be similar, but subtly different. It will take a lambda that computes an intermediate result, and a function that uses both the initial input, and the intermediate input, to compute a final result. It's basically the same as the Compose method, but the second function also gets to know about the first parameter:
public static Expression<Func<TFirstParam, TResult>>
Combine<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], param)
.Replace(second.Parameters[1], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Okay, now that we have all of this, we get to use it. First thing we'll do is create a static constructor; we won't be able to inline all we have to do into the field initializer. (The other option is to make a static method that computes this, and have the initializer call it.)
After that we'll create an expression that take a person and returns it's address. It's one of the missing puzzle pieces in the expressions that you have. Using that, we'll compose that address selector with the AddressDto selector, and then use Combine on that. Using that we have a lambda that takes a Person and an AddressDTO and returns a PersonDTO. So in there we have basically what you wanted to have, but with an address parameter given to us to assign to the address:
static PersonDto()
{
Expression<Func<Person, Address>> addressSelector =
person => person.Address;
Selector = addressSelector.Compose(AddressDto.Selector)
.Combine((entity, address) => new PersonDto
{
Id = entity.Id,
Name = entity.Name,
Address = address,
});
}
First off, I would use only a single class to do this. No need for DTO class that does the same thing.
If you insist on having secondary DTO classes, then I would simply create extension methods for them. Ex: PersonDTO personDto = myPerson.ToDTO()

Categories

Resources