Friday, July 18, 2008

Extension Method + LINQ + Interface = Reusable Queries

One of the requirements of Suteki Shop is that admin users should be able to create various new entities (categories, products, content etc) before they are published on the public web site. To this end I created a simple interface, IActivatable:

public interface IActivatable
{
   bool IsActive { get; set; }
}

As you can see it's got a single property 'IsActive'. Any entity that needs to be active/inactive implements it. So my product class' partial class definition looks like this:

public partial class Product : IOrderable, IActivatable, IUrlNamed
{
 ....
}

I'll explain IOrderable and IUrlNamed later, but they do similar things. Each entity has a matching table in the database and each entity that implements IActivatable also has a bit column IsActive. Here are Category and Product:

Product_Category_DB

When I drag these tables onto the LINQ-to-SQL design surface, Product and Category classes are created with IsActive properties. I can make their matching partial classes implement IActivatable and the designer IsActive property satisfies the implementation requirement.

Now I can write a reusable LINQ query extension method for any IActivatable to filter only active items:

public static IQueryable<T> Active<T>(this IQueryable<T> items) where T : IActivatable
{
   return items.Where(item => item.IsActive);
}

Now every time I need a list of active items I can just chain the query with Active(),

var products = productRepository.GetAll().Active();

Remember the IOrderable and IUrlNamed interfaces that the Product partial class implemented? They work in exactly the same way. I explained IOrderable in a previous post, it's used to create a generic service to order and re-order lists of entities. IUrlNamed provides a generic way of querying entities given a unique string from the URL. For example, Suteki Shop has nice URLs for products: http://sutekishop.co.uk/product/East_End_Trilby. So there's an extension method:

public static T WithUrlName<T>(this IQueryable<T> items, string urlName) where T : IUrlNamed
{
   T item = items
       .SingleOrDefault(i => i.UrlName.ToLower() == urlName.ToLower());

   if (item == null) throw new ApplicationException("Unknown UrlName '{0}'".With(urlName));
   return item;
}

That allows us to look up products like this:

public ActionResult Item(string urlName)
{
   Product product = productRepository.GetAll().WithUrlName(urlName);
   return View("Item", ShopView.Data.WithProduct(product));
}

3 comments:

D. Patrick Caldwell said...

Hey, I enjoyed your blog post. I've been writing a lot of extension methods lately myself. Sometimes you really have to be thankful to be a C# developer, you know?

Also, I was wondering if you've tried any syntax highlighters? I use one from google code and I have a pretty easy one liner you can add to your template. If you'd like to use it, feel free.

Mike Hadlow said...

Thanks Patrick!

I've started using a livewriter plugin for code samples more recently. If you look at some newer posts you can see that the code is a lot prettier.

Unknown said...

Hi Mike,

Thanks for your tip!
I´ve started using EF recently but i´m still amateur user...

Reading your posted comment i thought if is possible to customize a "where" condition for my all queries results.

Assuming all entities have a common property called "CompanyID", i would like to define a global where condition by Session application value for all of them.

This way i could make any query sintaxes and always get those filtered records for my current logged Company.

I would like something that was not necessary to specify ".CurrentCompany()" like your example ".Active()".

These filters would be applied implicitly...

Thanks your patience and attention!

Rubens Cury