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 for Non Linq to objects

Daniel WertheimDaniel Wertheim

This is an update to my post “C#, Clean up your Linq-queries and lambda expressions", where I got a comment that it will not work other than against Linq to objects. That is somewhat true. It will work but the query will fetch all posts from the database and then apply the where clause to objects in memory. In my case I only needed in memory but I thought, why not provide an example with Entity framework as well. This example will be targetting Entity framework Code first, CTP 5.

The test

[Test]
public void AgainstEfCodeFirstCtp5()  
{
  using (var dbContext = new StorageContext())
  {
    DbDatabase.SetInitializer(
      new DropCreateDatabaseAlways<StorageContext>());

    foreach (var customer in _customers)
      dbContext.Customers.Add(customer);

    dbContext.SaveChanges();
  }

  using (var dbContext = new StorageContext())
  {
    //Will not call the database
    var bonusCustomers = dbContext.Customers.Where(new IsBonusCustomer());

    //Will invoke call to database
    CollectionAssert.AreEqual(
      _expectedBonusCustomers.Select(c => c.Id),
      bonusCustomers.Select(c => c.Id));
  }
}

The sql-query will not be executed against the database until I access the deferred executable collection “bonusCustomers”. The query will look like this (interceptet with the Sql Profiler).

SELECT  
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]  
WHERE  
(
  (0 = [Extent1].[NumOfYearsAsMember])
  AND
  ([Extent1].[CashSpent] >= 3000)
)
OR  
(
  ([Extent1].[NumOfYearsAsMember] > 0)
  AND 
  (
    (
      [Extent1].[CashSpent] /
      [Extent1].[NumOfYearsAsMember]
    ) >= 5000
  )
)

It matches our rule of the c-sharp code, but the generated Sql will of course brake, since it will give a divide by zero for the customers with less than one year of membership, but that is not the point of this blog post.

The changes from the last blog post

Instead of letting the implicit operator return a Func<T> it now returns Expression<Func<T>>. To let the Evaluate member work I also compiled the lambda to a Func.

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

  public Func<T, bool> Compiled { get; private set; }

  public ExpressionRule2(
    Expression<Func<T, bool>> expression)
  {
    Expression = expression;
    Compiled = expression.Compile();
  }

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

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

Yes, I know, not the best naming with ExpressionRule2 but it’s a quick hack to get the blog post compatible with non linq to object sources. There’s actually nothing more to it. Of course I have put up a custom DbContext for Entity framework Code-first CTP 5, as well as added an Id to the customer.

public class StorageContext : DbContext  
{
  public DbSet<Customer> Customers { get; set; }

  public StorageContext()
    : base(@"data source=.;initial catalog=NiceLambdas;integrated security=SSPI;")
  {
  }
}
public class Customer  
{
  public int Id { get; set; }
  public int NumOfYearsAsMember { get; set; }
  public int CashSpent { get; set; }
}

Why this solution at all?

What this gives me:

Happy coding!

//Daniel

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

Comments