Today a friend of mine asked me to explain (via email) a few things to another developer regarding some things I have done with the Entity Framework (EF). I figured since I already typed it up I might as well share it here as well. So here it is.
I have had 2 issues with using EF4 which I have found creative and effective work around for.
Externalizing filters
The first issue, which also exists when using other frameworks, is that you end up with an explosion of methods (and usually a lot of duplicated code). For instance let’s say you have a table to store users. This table has an ID, and Email field and, and let’s and OpenIdUrl. So you end up with the following methods.
User GetUserById(long id) User GetUserByEmail(string email) User GetUserByOpenIdUrl(string openIdUrl)
Usually you’ll find that each of these methods have the exact same implementation except a different where clause. Now there is a bigger problem in the fact that you cannot anticipate everything that the user may want to filter on. Because of this and the dynamic nature of IQueryable I have employed a technique using Expression Trees to allow the user to declare the exact filter to be used via a lambda expression. So for the users it’s very slick. For example I could create the following method
public User GetUser(Expression> filter) { if (filter == null) { filter = Const .LinqExpression.LinqExpressionFuncAlwaysTrue; } User foundUser = null; using (InlineTasksContext dbContext = new InlineTasksContext()) { dbContext.Connection.Open(); foundUser = dbContext.Users.Where(filter).SingleIfExists(); } return foundUser; }
In this case I’m allowing the user to pass in a filter that is applied to the query. Here is how I could implement those three if I chose to as well.
public User GetUserById(long id) { return this.GetUser(user => user.Id == id); } public User GetUserByEmail(string email) { if (email == null) { throw new System.ArgumentNullException("email"); } return GetUser(user => user.Email == email); } public User GetUserByOpenIdUrl(string openIdUrl) { if (string.IsNullOrEmpty(openIdUrl)) { throw new ArgumentNullException("openIdUrl"); } return GetUser(user => user.OpenIdUrl == openIdUrl); }
Note in the first method I am using some other helpers I created. Specifically the Const
extension method. I use the AlwaysTrue expression so that way I don’t have to be worried about doing an if and having to maintain 2 select statements. And the other (SingleIfExists) is used to just return null instead of blowing up if no elements exist in the sequence.
Since they are not the purpose of this email I’ve just put them at the bottom so that you can read if you are interested.
As Keith mentioned to me a while back Expression trees are not serializable so you still end up with this problem if you are using a service based solution. But you could try and use the sample Expression Tree Serialization to take care of this for you, I’ve never tried it, but it’s on my TODO list.
.Include
My next issue is that you are forced to declare what navigation properties (i.e. table links) should be included in the query. This is done using the .Include method. So back to our GetUser example, how can a framework know what tables values should be extracted for? The end result is that the framework creator just includes a bunch of tables and 90% of the time the result is just wasted resources because the other data is never touched. Better is to provide a default which only includes the most commonly used tables and another method that allows them to specify what they want. Here is a sample for a GetTasksByUserId method
public IListGetTasksByUserId(long userId, IEnumerable includeList) { using (InlineTasksContext dbContext = new InlineTasksContext()) { var result = (from t in dbContext.Tasks .Include(includeList) .Where(t=>t.User.Id == 1) select t) .ToList(); return result; } } In this example I’m using an extension method for.Include which is defined as follows. public static ObjectQuery Include (this ObjectQuery query, IEnumerable includes) { if (query == null) { throw new System.ArgumentNullException("query"); } if (includes != null) { foreach (string include in includes) { query.Include(include); } } return query; }
So the idea is to create a framework which is both robust and easy to use, but at the same time is not overly assumptive about what the users want. I think a lot more can be done with EF and its dynamic nature.
Does that cover what you were intending Keith? Any questions/comments please send them my way.
Here are those methods I mentioned above.
public static class LinqExtensions { public static T SingleIfExistsSayed Ibrahim Hashimi(this IQueryable query) { if (query == null) { throw new System.ArgumentNullException("query"); } T result = default(T); if (query.Count() > 0) { result = query.Single(); } return result; } } //---------------------------------------------------------------------------------------------------- public class Const { public static class Predicate { public static Predicate AlwaysTrue { get { return x => true; } } public static Predicate AlwaysFalse { get { return x => false; } } } public class LinqExpression { public static Expression > LinqExpressionFuncAlwaysTrue { get { return x => true; } } } }
Comments are closed.