Creating reusable Linq filters (predicate builders for Where) that can be applied to different types

A way to create reusable Linq filters (predicate builders for Where clauses) that can be applied to different types of objects. The fields of objects to be filtered are specified using MemberExpression.





The method is suitable for Entity Framework, including Async operations.





Main idea. What is a reusable filter?

For example, there are orders:





class Order { 
	public DateTime Start { get; set; }
	public DateTime? End { get; set; }
}
      
      



Let you need to find all the orders that will be valid in the next 7 days.





Using a reusable filter builder (if it were implemented), you can find orders like this:





var ordersFiltred = orders
	.WhereOverlap(
		//   MemberExpressions
		//      
		fromField: oo => oo.Start,
		toField: oo => oo.End,

		//   
		from: DateTime.Now,
		to: DateTime.Now.AddDays(7))
	.ToList();
      
      



WhereOverlap . , :





class Trip { 
	public DateTime? From { get; set; }
	public DateTime? To { get; set; }
}
      
      



var tripsFiltred = trips
	.WhereOverlap(
		//   MemberExpressions
		//      
		fromField: oo => oo.From,
		toField: oo => oo.To,

		from: DateTime.Now,
		to: DateTime.Now.AddDays(7))
	.ToList();
      
      



- , , -. ( ) WhereOverlap.





.





WhereOverlap, . , WhereOverlap, “”, “”. .





:





class Payout { 
	public decimal Total { get; set; }
	public bool UnderControl { get; set; }
}

class Premium {
	public decimal Sum { get; set; }
	public bool RequiresConfirmation { get; set; }
}
      
      



:





class UnderControlPayFilter {
	readonly decimal Limit;
	public UnderControlPayFilter(decimal limit) {
		Limit = limit;
	}

	public Expression<Func<TEnt, bool>> Create<TEnt>(
		Expression<Func<TEnt, decimal>> sumField) {

		// GreaterOrEqual -  
		// GreaterOrEqual -  extension,  
		//  -    (Expression sumField)
		//  -       (Limit)
		return sumField.GreaterOrEqual(Limit);
	}
}
      
      



UnderControlPayFilter :





//      
//
//   ( 1000)    ,
//  UnderControlPayFilter   IoC-.
//    (  )
//   
var underControlPayFilter = new UnderControlPayFilter(1000);


//
//     

var payoutPredicate =
	underControlPayFilter.Create<Payout>(pp => pp.Total);

// ,  , payouts -  ,
//       Entity Framework DbSet 
var payouts = new[] {
	new Payout{ Total = 100 },
	new Payout{ Total = 50, UnderControl = true },
	new Payout{ Total = 25.5m },
	new Payout{ Total = 1050.67m }
}
.AsQueryable()
.Where(payoutPredicate)
.ToList();


//
//     

var premiumPredicate =
	underControlPayFilter.Create<Premium>(pp => pp.Sum);

// ,  , premiums -  ,
//       Entity Framework DbSet 
var premiums = new[] {
	new Premium{ Sum = 2000 },
	new Premium{ Sum = 50.08m },
	new Premium{ Sum = 25.5m, RequiresConfirmation = true },
	new Premium{ Sum = 1070.07m }
}
.AsQueryable()
.Where(premiumPredicate)
.ToList();
      
      



, GreaterOrEqual extension:





public static class MemberExpressionExtensions {
    public static Expression<Func<TEnt, bool>> GreaterOrEqual<TEnt, TProp>(
        this Expression<Func<TEnt, TProp>> field, TProp val)
            => Expression.Lambda<Func<TEnt, bool>>(
                Expression.GreaterThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))), 
                field.Parameters);
}
      
      



extension- LessOrEqual, Equal, HasNoVal .





“” “”

, , , .





UnderControlPayFilter:





class UnderControlPayFilter {
	readonly decimal Limit;
	public UnderControlPayFilter(decimal limit) {
		Limit = limit;
	}

	public Expression<Func<TEnt, bool>> Create<TEnt>(
		Expression<Func<TEnt, decimal>> sumField,
		Expression<Func<TEnt, bool>> controlMarkField) {

			// PredicateBuilder   (. )
			return PredicateBuilder.Or(
				sumField.GreaterOrEqual(Limit),
				controlMarkField.Equal(true));
	}
}
      
      



:





//  

var payoutPredicate =
	underControlPayFilter.Create<Payout>(
		sumField: pp => pp.Total,
		controlMarkField: pp => pp.UnderControl);

//  

var premiumPredicate = 
	underControlPayFilter.Create<Premium>(
		sumField: pp => pp.Sum,
		controlMarkField: pp => pp.RequiresConfirmation);
      
      



PredicateBuilder “A universal PredicateBuilder” Pete Montgomery.





To make your own reusable filters you only need PredicateBuilder and MemberExpressionExtensions . Just copy them to your project. Reusable filters can be styled as an extension (as in WhereOverlap), as a static helper, or as a class (as in UnderControlPayFilter).





I've made a couple of reusable filters - GitHub , NuGet (includes PredicateBuilder and MemberExpressionExtensions).








All Articles