Sunday, February 23, 2014

Homemade Lightweight Automapper Part 3

If you're wondering why it has taken me so long to write up part 3, it's for a number of reasons. Firstly, procrastination, secondly I've been pretty busy with other things, thirdly, while writing the 2nd part of the series, we were actually reiterating over the process we use to do projections, because of some of the drawbacks of the method I've described and fourthly, I've been learning a whole bunch on delegates, expressions and lambdas in C# recently to try and find out just how far down the rabbit hole I can go. I should also note, that the AutoMapper library is now working correctly with IQueryables, so this is all really just a mental exercise at this point. As such, I'll finish off the series as it was intended, but I'll also continue it on, and get let you guys see my journey with me.

So when we left off, we had the infrastructure in place with our list of projections ready to go. We were just missing the code to let us use them!

There are some basic ways we need to get the data when it comes to linq, and they really boil down to 3 different methods. 1.) We obviously need our IQueryable<Model> to build up our SQL in a monadic fashion. 2.) We need our IEnumerable<Model> for any Enumerable object that is not an IQueryable, this includes List and other such containers. 3.) We need our individual Models.

So. We need 3 different Project() methods to encompass each type. The 'meat' of these methods are going to look pretty much the same for any projection type code you'll write. Essentially, we need to get the projection, and apply it to our objects.

We want to write these these extension methods so that they can work directly on the IQueryable, IEnumerable, and Domain types, so our declarations would be something like the following:

static IQueryable<TDest> Project<TSource, TDest>(this IQueryable<TSource> DomainQuery);
static IEnumerable<TDest> Project<TSource, TDest>(this IEnumerable<TSource> DomainList);
static TDest Project<TSource, TDest>(this TSource Domain);
This gives us a syntax of:

query.Project<Domain, Projection>();
list.Project<Domain, Projection>();
model.Project<Domain, Projection>();
respectively. It's pretty verbose. The problem here is that when working with generic methods, you can only hide the 'generic' signature, if all types can be implied by the compiler. In our case, we're only using TSource, so TDest can't be implied, and we have to use both in our method signature.

We can get around this by redefining our methods using the out keyword like so:

static void Project<TSource, TDest>(this IQueryable<TSource> DomainQuery, out IQueryable<TDest> ProjectionQuery);
static void Project<TSource, TDest>(this IEnumerable<TSource> DomainList, out IEnumerable<TDest> ProjectionList);
static void Project<TSource, TDest>(this TSource Domain, out TDest Projection);
This lets us do the following:

IQueryable<Projection> pQyery;
query.Project(out pQuery);
It removes the generic signature nicely, but you get stuck with the ugly out doing it this way.

Jimmy Bogard, author of the Automapper Library, actually gets around this by using 2 functions, a Project() which wraps the object into an IProjection interface then To() to do the actual conversion, so you end up with model.Project().To(); which is actually pretty clean. With a little more infrastructure you can clean this up just the tiniest bit more, but I'll save that for another post.

We can clean this up, but it's going to take some refactoring that I'll save for later. If you'll remember back in part 2 we had set up a dictionary map for our projections that could be called with the syntax projections[typeof(TSource)][typeof(TDest)]. There is one problem, however. Our dictionary currently only holds Expressions, not Expression<T>'s. We'll need to cast it before we can return it. Thankfully it's a pretty straightforward cast. Lets wrap that in a function for kicks and giggles.

Expression<Func<TSource, TDest>> GetProjection<TSource, TDest>() {
   Expression e = projections[typeof(TSource)][typeof(TDest)];

   return e as Expression<Func<TSource, TDest>>;
}
LinqToSQL, Entity Framework, and other's of that ilk use expression trees to write their SQL queries, while LinqToObjects uses delegates. Both of these are defined by lambdas, but what goes on behind the scenes is very different. Thankfully MS have made it very simple to compile our Expression into a delegate on the fly using a Compile() method, so passing around Expressions gives us the best of both worlds.

   Expression<Func<TSource, TDest>> e;
   Func<TSource, TDest> dele = e.Compile();
From what I understand the compilation itself is a little slow (not as slow as reflection), but it's not all that noticeable for 90% of what we would do with it. To go the other way from delegate to expression tree is a rather nasty mess of using reflection to determine what the delegate is doing and then building the immutable expression tree piece by piece. I wouldn't recommend it, dynamically building expression trees is a nasty business, as is reflection.

Armed with this knowledge, defining our IQueryable and IEnumerable projection methods becomes pretty simple.

   static void IQueryable<TDest> Project<TSource, TDest>(this IQueryable<TSource> Query) {
      Expression<Func<TSource, TDest>> projection = GetProjection<TSource, TDest>();

      return Query.Select(projection);
   }

   static void IEnumerable<TDest> Project<TSource, TDest>(this IEnumerable<TSource> List) {
      Expression<Func<TSource, TDest>> projection = GetProjection<TSource, TDest>();

      return List.Select(projection.Compile());
   }
Linq Extensions already give us our handy Select method with takes a delegate for IEnumerables or an Expression for IQueryables respectively. Our single object is going to be a little less straight forward. In this case we want to use our delegate and we want to run it directly on the object. Keep in mind that our delegate is a lambda, which is another way of saying that it is an anonymous function.

I'll write up a blog post on delegates soon. It is too much to go into detail here unfortunately. Suffice to say using a delegate is as easy as calling its Invoke method where the parameters are the values/objects to operate on. In our case:

   static void TDest Project<TSource, TDest>(this TSource Domain) {
      Expression<Func<TSource, TDest>> projection = GetProjection<TSource, TDest>();

      return projection.Compile().Invoke(Domain);
   }
That's really all there is to it. Those three projection functions take any object you need and convert it into another type using an expression. When we move away from the dictionaries, those will pretty much stay the same. Now as promised, I'll list some of the issues with the dictionary approach, though I'll save the solution for another post.

First and foremost, any time you are dealing with a type lookup, you've taken over a job that really belongs to the compiler. As such, any time you try and Project, you run the risk of throwing an exception when the lookup fails. Any issues therefore, are found at run time rather than compile time. This isn't necessarily a bad thing, but it does entirely remove the #1 benefit of using a strongly typed language, and it can be a little more difficult to debug as a result. There are legitimate uses for such a lookup (I'll demonstrate one in the next blog post as a matter of fact) but overall, when you find yourself relying on this pattern, you should really stop and ask yourself if there is a better way.

Secondly, creating lookups like this is much better suited to data rather than code. It's far nicer to load some data file, and loop through the contents, adding each entry to a lookup dictionary than it is to have pages of code where all you're doing is defining said dictionary manually. if I recall correctly in my current project, we have a over 300 view models, and while currently our dictionaries are broken out a little bit, that's still a lot of lines where certain entries could get lost or forgotten.

Thirdly, it pushes our definitions away from where they are being defined. If you're missing a projection in the dictionary, you have to go to your Projector class, add it there and rebuild. This sort of dependency means that you cannot have your class as a standalone code block, and also means that you'll be jumping between different class files any time you want to make a projection. It would be much nicer if we could remove the dependency altogether, and it would be easier to test.

I'll address each of these in a later post.

No comments:

Post a Comment