In my previous post Multiple search criteria searching using Linq to SQL I talked about a way to implement multiple search criteria queries using LINQ to SQL.
Because I am doing some work using the ASP.NET MVC Framework, I looked into patterns to make a loosely coupled data layer. Ofcourse I checked out Rob Connery’s blog, he created the MVC Storefront (now Kona) the code can be found on MVC Sample Apps on Codeplex. Rob is leveraging the Repository pattern, this pattern provides dependency-free access to data of any type. I saw the screen cast where Ayende Rahien talks about ‘Filters and Pipes’.
Rob implements the filters in the MVC Storefront, I really like this approach, better than the approach in my previous post Multiple search criteria searching using Linq to SQL because it’s much cleaner, it is possible to ‘chain’ multiple criteria, but every criteria has it’s own extension method, thus following the single responsibility principle.
In my previous post in some cases I did too much in one method, I build up an IQueryable<post> for four search criteria, which breaks the Single Responsibility principle for one.
Using filters, which are extension methods that specify a filter on an IQueryable of something, makes it possible to let the calling party build up whatever they need. I know this can sound confusing, but I feel the following code example explains much better.
In my previous post, I called a few methods, and build up a IQueryable that way to satisfy every search criteria in the query.
2: public void Search_For_mvc_in_Title_And_Use_Paging_Test()
5: MvcBlogDataContext repository = new MvcBlogDataContext("Data Source=.;Initial Catalog=MvcBlog;Persist Security Info=True;");
7: var postsQuery = from p in repository.Posts
8: select p;
10: postsQuery = GetPostsQuery(postsQuery, "mvc", "", "", null);
11: postsQuery = GetPostsPagingQuery(postsQuery, 0, 5);
13: List<Post> posts = postsQuery.ToList();
15: Assert.IsNotNull(posts, "DataContext did not return posts when searching for 'mvc' in title");
16: Assert.AreEqual(posts.Count, 2, "DataContext did not return 2 posts when searching for 'mvc' in title");
Would not it be cool if we could use a fluent interface-like way to query the data, so it will be obvious to what we want to query?
Something like the code (also in a Unit test manner like the previous post) in listing 2:
2: public void Search_For_mvc_in_Title_With_Paging_Test()
4: List<Post> posts = GetPosts().WithTitleLike("mvc")
7: .WithPaging(0, 5)
10: Assert.IsNotNull(posts, "DataContext did not return posts when searching for 'mvc' in title");
11: Assert.AreEqual(posts.Count, 2, "DataContext did not return 2 posts when searching for 'mvc' in title");
I think it is clear that the code in listing 2 is far more readable, than the code in listing 1. Another advantage is that it is easy to reuse every part of the query whenever it is needed.
First I create a method that returns all Posts present in the database as an IQueryable<Post>. By the way, I am not using Dependency Injection, or Inversion of Control in this example, because it does not help in explaining the filters concept. BUT I think that when using this technique in a real world application, it is a good thing to use IoC (StructureMap, Windsor, Ninject, Unity, whatever…).
1: public IQueryable<Post> GetPosts()
3: var postsQuery = from p in repository.Posts
4: select p;
5: return postsQuery;
Leveraging extension methods, a .NET Framework 3.0 feature in combination with the IQueryable<T> interface, it is possible to create the filters. The class needs to be static and public, the extension methods also need to be static and public.
The first parameter specifies which type the method operates on and needs to be preceded by the ‘this’ modifier.
1: public static class PostFilters
3: public static IQueryable<Post> WithTitleLike(this IQueryable<Post> postsQuery,
4: string title)
6: if (!string.IsNullOrEmpty(title))
7: postsQuery = postsQuery.Where(p => p.Title.Contains(title));
9: return postsQuery;
12: public static IQueryable<Post> WhereTagsContain(this IQueryable<Post> postsQuery,
13: string tags)
15: if (string.IsNullOrEmpty(tags))
16: return postsQuery;
18: return postsQuery.Where(p => p.Tags.Contains(tags));
21: public static IQueryable<Post> WhereBodyContains(this IQueryable<Post> postsQuery,
22: string bodyText)
24: if (!string.IsNullOrEmpty(bodyText))
25: return postsQuery;
27: return postsQuery.Where(p => p.Body.Contains(bodyText));
30: public static IQueryable<Post> IsCreatedOn(this IQueryable<Post> postsQuery,
31: DateTime? createdOn)
33: if (!createdOn.HasValue && createdOn.Value == DateTime.MinValue)
34: return postsQuery;
36: return postsQuery.Where(p => p.CreatedOn.Value.Date == createdOn.Value.Date);
39: public static IQueryable<Post> WithPaging(this IQueryable<Post> postsQuery,
40: int? startRow,
41: int? rowCount)
43: if ((!startRow.HasValue) && (!rowCount.HasValue || rowCount.Value == 0))
44: return postsQuery;
46: return postsQuery.Skip((int)startRow).Take((int)rowCount);
In listing 3 it is obvious that every method has its own responsibility, it is easily maintainable and very readable. I think this is an elegant solution for the problem I tried to solve in my previous post, I found this better technique and want to share.
My thoughts exactly…