2

I am trying to refactor some code for a generic repository, that passes in a filter object that will filter data, as well as page, sort etc.

Each inheriting Filter (Such as CustomerFilter) has the option of defining its own Expression Filter which will get applied by the base class in the repository.

So the customer filter will have properties like this:

public string CustomerId { get; set; }

public override Expression<Func<object, bool>> Predicate => c => ((Customer)c).Id == CustomerId;

Then the repository will run the filter in the repository, a bit like this (it's not generic yet!):

using (var context = new CustomerContext())
{
      return await Filter<Domain.Customer>.ApplyAsync(filter, context.Customers.AsQueryable()).ConfigureAwait(false);
}

This works ok, but I need a way to build the expression in a better way for more complex examples.

For example, the filter may allow to filter the customers on the state, but only if its set.

public string CustomerId { get; set; }

public State? CustomerState { get; set; }

public override Expression<Func<object, bool>> Predicate => c => (((Customer)c).Id == CustomerId) && (((Customer)c).State == CustomerState ?? (Customer)c).State);

This not only becomes a mess, but also there's a lot of unnecessary casting and parenthesis. So what I'd like to do is an expression builder in the getter, that would build the expression in a cleaner way, such as if(State != null) { CustomerState == State; }. But that's where I'm not sure how to proceed, so if anyone can help me out, I'd appreciate it.

Tom
  • 603
  • 1
  • 6
  • 17
  • 3
    Well the casting would go away if you made the repository generic and use the correct type instead of `object` everywhere. – DavidG Jun 19 '19 at 08:28
  • If you want to use an `if` statement, you no longer have an expression, so that won’t work. – poke Jun 19 '19 at 08:31
  • 1
    Also, perhaps you are better off returning an enumerable of expressions, that way your override can determine if it needs to return another expression if the state is set for example. Your apply method can then loop over the expressions one by one and add them in. – DavidG Jun 19 '19 at 08:32
  • This kind of complexity is exactly why I believe that a generic repository is an anti-pattern. I'd be thinking about restructuring my application into a data access layer then a querying layer on top that contains your explicit filtering logic. – Chris Pickford Jun 19 '19 at 08:32
  • You may also want to look into the specification pattern. – DavidG Jun 19 '19 at 08:57
  • Use a predicate builder library. See [this answer](https://stackoverflow.com/a/36247259/861716). – Gert Arnold Jun 19 '19 at 09:17
  • @DavidG I have successfully used your ienumerable example to work to good effect. Thanks. – Tom Jun 20 '19 at 15:38

2 Answers2

2

If you want to combine multiple "Conditions" to apply to Where clause you can use PredicateBuilder from LinqKit library

Here is an example to combine two conditions with "Or" clause

System.Linq.Expressions.Expression<Func<Domain.Question, bool>> codition1 = e => e.CategoryId == 1;
System.Linq.Expressions.Expression<Func<Domain.Question, bool>> condition2 = e => e.CategoryId == 2;
var combinedCondition = LinqKit.PredicateBuilder.Or(codition1, condition2);
//using combined condition in where clause....
queryable = queryable.Where(combinedCondition);

You can use other methods of PredicateBuilder class such as "And" to get the combined condition you want...

Namig Hajiyev
  • 1,117
  • 15
  • 16
0

You can combine expressions with Linq Expressions API:

public Expression<Func<Customer, bool>> BuildExpression()
{
    Expression<Func<Customer, bool>> predicate = c => c.Id == CustomerId;

    if (State != null)
    {
        var parameter = predicate.Parameters.First();
        var newBody = Expression.AndAlso(
            predicate.Body,
            Expression.Equal(
                Expression.PropertyOrField(parameter, nameof(State)),
                Expression.Constant(State)
            ));

        predicate = Expression.Lambda<Func<Customer, bool>>(newBody, parameter);
    }

    return predicate;
}

In code above predicate is a base expression that will be used if State is null. But when State is set we extract expression parameter and add && c.State == State to predicate body

Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
  • Now imagine doing this kind of code for a whole bunch of different repositories, I would not recommend it. – DavidG Jun 19 '19 at 08:56
  • @DavidG it can be automated with reflection: you can iterate through each property of a filter and if it value is not null include it into resulting expression – Aleks Andreev Jun 19 '19 at 08:59
  • Yes, but why bother? [This comment](https://stackoverflow.com/questions/56663177/expressionfuncobject-bool-as-property#comment99895133_56663177) provides a simpler option. – DavidG Jun 19 '19 at 09:00
  • @DavidG I didn't saw it. You are right it simpler. I think you should post it as an answer – Aleks Andreev Jun 19 '19 at 09:16