danielwertheim

danielwertheim


notes from a passionate developer

Developer that lives by the mantra "code is meant to be shared".

Share


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

C#, Clean up your Linq-queries and lambda expressions

Daniel WertheimDaniel Wertheim

Last week something caught my eyes. How scattered business logic can become if you let your binary expressions be used “here and there” when matching entities against certain rules. Logic for a single entity, e.g Customer can be used all over your codebase. This article will show you a way to get around this and to get a central repository of rules/predicates that can be applied to entities and makes your daily unit test writing easier. I will use simple operator overloading for accomplishing this.

The example code can be downloaded here.

Part 2How to get it to work against non Linq to object sources

I have put together a testfixture with test going from ugly code to “more” beautiful code. The example is fictive and therefore not the best example for what I’m showing. The problem below might be best solved with simple code as Customer.IsBonusCustomer() which then contains the logic for determening this. But I want to show you a way to handle this if you instead want the logic outside the customer and don’t want the lambdas/linq queries scattered all over your codebase.

In the code I have made use of Func but you can of course use Expression<Func> if you need to parse the expression tree.

The problem

Identify customers that are considered to be bonus customers.

Rules:

The solution

Lets start with the entity example which of course intentionally have been made simple.

public class Customer  
{
    public int NumOfYearsAsMember { get; set; }
    public int CashSpent { get; set; }
}

The ugly ways

First, lets be clear: “No I don’t mean the Linq or Lambda syntax in it self when I say ugly”. I mean ugly as you get “small” (in best case) pieces of logic lying around in your queries.

[Test]
public void TheUglyWayUsingLinq()  
{
  var bonusCustomers = 
    from c in _customers
    where (
      c.NumOfYearsAsMember == 0 &&
      c.CashSpent >= 3000
    ) ||
    (
      c.NumOfYearsAsMember > 0 &&
      (c.CashSpent / c.NumOfYearsAsMember) >= 5000
    )
    select c;

  CollectionAssert.AreEqual(_expectedBonusCustomers, bonusCustomers);
}
[Test]
public void TheUglyWayUsingLambdas()  
{
  var bonusCustomers = _customers.Where(c =>
    (
      c.NumOfYearsAsMember == 0 &&
      c.CashSpent >= 3000
    ) ||
    (
      c.NumOfYearsAsMember > 0 &&
      (c.CashSpent / c.NumOfYearsAsMember) >= 5000)
    );

  CollectionAssert.AreEqual(_expectedBonusCustomers, bonusCustomers);
}

Both of these examples are rather simple (seen to what you meet in different enterprise applications) but yet rather hard to read. Well of course not if you concentrate on just the algorithm but as a whole it is. First lets make it somewhat nicer. Lets extract the selectors so that we get a name in the Linq and Lambda expressions.

[Test]
public void SomewhatNicerUsingLinq()  
{
  Func<Customer, bool> isFirstYearBonusCustomer = c => 
    c.NumOfYearsAsMember == 0 && c.CashSpent >= 3000;

  Func<Customer, bool> isAfterFirstYearBonusCustomer = c => 
    c.NumOfYearsAsMember > 0 &&
    ((c.CashSpent / c.NumOfYearsAsMember) >= 5000);

  var bonusCustomers = from c in _customers
    where isFirstYearBonusCustomer(c) ||
    isAfterFirstYearBonusCustomer(c)
    select c;

  CollectionAssert.AreEqual(_expectedBonusCustomers, bonusCustomers);
}
[Test]
public void SomewhatNicerUsingLambdas()  
{
  Func<Customer, bool> isFirstYearBonusCustomer = c =>
    c.NumOfYearsAsMember == 0 && c.CashSpent >= 3000;

  Func<Customer, bool> isAfterFirstYearBonusCustomer = c =>
    c.NumOfYearsAsMember > 0 &&
    ((c.CashSpent / c.NumOfYearsAsMember) >= 5000);

  var bonusCustomers = _customers.Where(c =>
    isFirstYearBonusCustomer(c) ||
    isAfterFirstYearBonusCustomer(c));

  CollectionAssert.AreEqual(_expectedBonusCustomers, bonusCustomers);
}

Slightly better and if we put the selectors in a class they can be reused.

The more beautiful way

Now lets write a test in a way I would like to read it.

[Test]
public void MaybeMoreBeautifulUsingRules()  
{
  var bonusCustomers = _customers.Where(new IsBonusCustomer());

  CollectionAssert.AreEqual(_expectedBonusCustomers, bonusCustomers);
}

How can I accomplish this?

Well it’s easy. Create a class that implements a implicit operator overloading for Func which then allows you to pass instances of this class into the IEnumerable.Where function.

public class ExpressionRule<T>  
  : IRule<T> where T : class
{
  public Func<T, bool> Expression { get; private set; }

  public ExpressionRule(Func<T, bool> expression)
  {
    Expression = expression;
  }

  public static implicit operator Func<T, bool>(ExpressionRule<T> item)
  {
    return item.Expression;
  }

  public bool Evaluate(T item)
  {
    return Expression(item);
  }
}

The next step is to create one representing the rule at hand IsBonusCustomer.

public class IsBonusCustomer  
  : ExpressionRule<Customer>, IIsBonusCustomer
{
  public IsBonusCustomer() : base(c =>
    (c.NumOfYearsAsMember == 0 && c.CashSpent >= 3000) ||
    (c.NumOfYearsAsMember > 0 && (c.CashSpent / c.NumOfYearsAsMember) >= 5000))
  { }
}

Again, if you need to parse the expression tree the ExpressionRule class would look something like this.

public class ExpressionRule<T>  
  : IRule<T> where T : class
{
  public Func<T, bool> Expression { get; private set; }

  public ExpressionRule(Expression<Func<T, bool>> expression)
  {
    //Do something with the lambda
    //...
    //...
    Expression = expression.Compile();
  }

  public static implicit operator Func<T, bool>(ExpressionRule<T> item)
  {
    return item.Expression;
  }

  public bool Evaluate(T item)
  {
    return Expression(item);
  }
}

Easier to test

By having the solution above you open up your code to be easy to test. Lets say we have a PriceLocator that uses this rule to determine the price for a customer. We want to test the discount algorithm and be sure that the rule for determining if a customer is a bonus customer, always evaluates to true.

public class PriceCalculator  
{
  public IIsBonusCustomer IsBonusCustomer;

  public PriceCalculator()
  {
    IsBonusCustomer = new IsBonusCustomer();
  }

  public decimal Calculate(decimal basePrice, Customer customer)
  {
    return IsBonusCustomer.Evaluate(customer)
      ? basePrice * 0.8M
      : basePrice;
  }
}

Now I can put up a fake and inject it to the price locator, so that the code under test becomes isolated.

[Test]
public void CanBeEasilyBeFaked()  
{
  var alwaysBonusCustomerRuleFake = new Mock<IIsBonusCustomer>();
  alwaysBonusCustomerRuleFake
    .Setup(f => f.Evaluate(It.IsAny<Customer>()))
    .Returns(true);

  var priceCalculator = new PriceCalculator
  {
    IsBonusCustomer = alwaysBonusCustomerRuleFake.Object
  };
  var priceWithDiscount = priceCalculator.Calculate(100, new Customer());

  Assert.AreEqual(80, priceWithDiscount);
}

I can now of course also test the IsBonusCustomer rule.

[Test]
public void CanBeTestedInIsolation()  
{
  var firstYearBonusCustomer = new Customer
  {
    CashSpent = 3000,
    NumOfYearsAsMember = 0
  };

  var rule = new IsBonusCustomer();

  ExpressionAssert.EvalAsTrue(rule, firstYearBonusCustomer);
}

That’s it. Again the examples above might be overkill since you could have placed methods directly on the Customer, but….

Why this solution at all?

What this gives me:

//Daniel

Developer that lives by the mantra "code is meant to be shared".

Comments