Choosing the right abstractions

Choosing the right abstractions can be hard.

 

Lets take a good look at the next piece of code.

   1: public class WrongAbstraction


   2: {


   3:     public void Execute()


   4:     {


   5:         IEnumerable<Product> products = GetProducts();


   6:         PrintProducts(products);


   7:     }


   8:  


   9:     private void PrintProducts(IEnumerable<Product> products)


  10:     {


  11:         foreach (var product in products)


  12:         {


  13:             Console.WriteLine(product.ProductName);


  14:         }


  15:  


  16:         Console.WriteLine("Total number of products {0}", products.Count());


  17:     }


  18:  


  19:     private IEnumerable<Product> GetProducts()


  20:     {


  21:         using (var ctx = new NorthwindEntities())


  22:         {


  23:             return ctx.Products.ToArray();


  24:         }


  25:  


  26:     }


  27: }

 

That looks quite reasonable right?

In the Execute() function we first call the GetProducts() function. This loads the products from a database using entity framework ensuring the data is loaded by calling ToArray() on the IQueryable collection and destroys the entity framework context to close the collection.

Next the PrintProduct() functions takes the array of products and prints each product name and the total number of products.

Even though we are loading the  product data into an array, or a list if we would have called ToList(), we are passing it around as an IEnumerable of Product. And that seems to be the correct thing because the fact that the data is stored in an array or list is just an implementation detail of the GetProducts() function.

 

Trouble in paradise

Good as this may appear there is a problem with the code. It will run and execute as expected but if we have Resharper installed, and that is highly recommended, we will see the following warning.

 

image

 

What is the problem? Well the code takes an IEnumerable and we are enumerating once to print the products and another time to count the total number of products. You might argue that the error is not important because we have the data in memory so the second enumeration is not a problem. But if the IEnumerable is actually an IQueryable, ie the ctx.Products itself, it would try and do the database query twice. Just remove the ToArray() from the GetProducts() and you see that the code still compiles fine. However running the code will fail because the required SQLConnection is already closed.

Resharper will over to fix the error by turning the IEnumerable into an array or list first. This would solve the possible double query but not the problem that we are querying over a closed connection.

 

Fixing the problem with the right abstraction

Both the Resharper error message and the fact that the code compiles but fails at runtime is the effect of choosing the wrong abstraction.

 

Take a good look at the next code snippet:

   1: public class RightAbstraction


   2: {


   3:     public void Execute()


   4:     {


   5:         IReadOnlyCollection<Product> products = GetProducts();


   6:         PrintProducts(products);


   7:     }


   8:  


   9:     private void PrintProducts(IReadOnlyCollection<Product> products)


  10:     {


  11:         foreach (var product in products)


  12:         {


  13:             Console.WriteLine(product.ProductName);


  14:         }


  15:  


  16:         Console.WriteLine("Total number of products {0}", products.Count());


  17:     }


  18:  


  19:     private IReadOnlyCollection<Product> GetProducts()


  20:     {


  21:         using (var ctx = new NorthwindEntities())


  22:         {


  23:             return ctx.Products.ToArray();


  24:         }


  25:     }


  26: }



 



It’s almost the same right?



In fact the only change is to return an IReadOnlyCollection instead of an IEnumerable from the GetProducts() function. Making this small change has a huge benefit. We can no longer return an IQueryable, just remove the ToArray() and see the compile error. As a result Resharper is no longer worried about the double enumeration because it can’t produce a double database query. And both an array as a list implements IReadOnlyCollection so no other code changes are required.



 



Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>