Linq has been around for a while now, and it is arguably a very powerful tool to have at your disposal in your development arsenal. It allows very quick and easy traversal of IEnumerable and IQueryable based objects that you would otherwise have to use a loop to work with. First let's take a look at how linq works verses a traditional loop.
Loop Example
public List<Post> GetDraftPosts()
{
List<Post> posts = _postService.GetPosts();
List<Post> draftPosts = new List<Post>();
foreach (var post in posts)
{
if (post.IsDraft)
{
draftPosts.Add(post);
}
}
return draftPosts;
}
Linq IEnumerable Example
//At the top of the class make sure to add a using statement
using System.Linq;
...
public List<Post> GetDraftPosts()
{
IEnumerable<Post> posts = _postService.GetPosts();
Func<Post,bool> criteria = (x => x.IsDraft);
return posts.Where(criteria).ToList();
}
What you see above is a delegate function written in lambda notation. The parameter of the Where() method is also referred to as a predicate
. As you can see, linq can reduce the number of lines of code needed to produce the same result. However, this is a really poor approach to working with a data layer, because it would appear that the above code is looking up all posts, and then filtering down to what it needs. This is where IQueryable comes in.
IEnumerable vs IQueryable
IEnumerable is the interface that makes Linq To Objects
possible. IQueryable is the interface that makes Linq To Sql
possible. Both IQueryable and IEnumerable use deferred execution
which means that the results will not be evaluated until they moment they are needed. The difference between the two is that IQueryable will be executed in the database if possible, and IEnumerable will bring back all rows of a table and then filter it down in memory. This has massive performance implications.
Linq with IQueryable
IQueryable uses predicates of type Expression<Func<T,bool>
that are called expression trees
. Expression trees allow you to define linq expressions that target sources of data that implement IQueryable
public List<Post> GetDraftPosts(int userId = 0, int userTypeId = 0)
{
Expression<Func<Post,bool>> criteria = new Expression<Func<Post,bool>>();
if (userId > 0 && userTypeId > 0)
{
criteria = (x => x.IsDraft && x.UserId == userId && x.UserTypeId = userTypeId);
}
else if (userId > 0)
{
criteria = (x => x.IsDraft && x.UserId == userId);
}
else
{
criteria = (x => x.IsDraft);
}
//_postService.GetPosts() uses IQueryable under the hood, which is why we pass in an expression tree
IEnumerable<Post> posts = _postService.GetPosts(criteria);
return posts.ToList();
}
In the above example, the expression tree resembles very closely what we saw in the IEnumerable example, but the expression tree will be evaluated and converted into something an IQuerable datasource can use in the database. However, expression trees can result in cluttered looking branching logic where you repeat at least some part of the same logic multiple times. This is where a PredicateBuilder
comes in.
Combining Expression Trees
If you start experimenting with expression trees, you will quickly find yourself in the scenario illustrated above. Out of the box, .NET makes combining expression trees very complicated. That's where this author's solution comes in very handy https://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/. Once implemented you'll have access to a wide array of extension methods for expression trees. Let's take a look at the above example again with the PredicateBuilder implemented.
public List<Post> GetDraftPosts(int userId = 0, int userTypeId = 0)
{
Expression<Func<Post,bool>> criteria = new Expression<Func<Post,bool>>();
criteria = (x => x.IsDraft);
//Append more criteria
if (userId > 0)
{
criteria = criteria.And(x => x.UserId == userId);
}
//Append more criteria
if (userTypeId > 0)
{
criteria = criteria.And(x => x.UserTypeId = userTypeId);
}
//_postService.GetPosts() uses IQueryable under the hood, which is why we pass in an expression tree
IEnumerable<Post> posts = _postService.GetPosts(criteria);
return posts.ToList();
}
Now we can add each additional piece of criteria independently of each other as opposed to build from the most specific situation backwards. The code becomes more readable and it is less likely that a developer will make a mistake by putting logic in the wrong order. One important thing to notice is that the expression tree object is set to itself plus the extension method. If you just call the extension method without setting the expression tree to itself you will end up with no change to the object.