Google analytics script

Latest jQuery CDN with code tiggling.

Friday, 5 August 2011

BLToolkit MapResultSet builder

or How I refactored my seemingly identical methods with generic type parameters

As I've written in the past I'm using BLToolkit lightweight ORM on one of my projects. But some parts of it seem very silly actually and I can't really see why did they decide to do certain parts the way that they did. One of them being the configuration for multiple result sets. You know those where you write TSQL query that returns more than one result. BLToolkit has this nice DbManager mapping method called ExecuteResultSet() that takes an array of MapResultSet objects. And these you have to prepare yourself first so mapper will actually populate your object lists. Their example looks like this (just the relevant code):

   1:  List<Parent> parents = new List<Parent>();
   2:  MapResultSet[] sets = new MapResultSet[3];
   3:   
   4:  sets[0] = new MapResultSet(typeof(Parent), parents);
   5:  sets[1] = new MapResultSet(typeof(Child));
   6:  sets[2] = new MapResultSet(typeof(Grandchild));
   7:  // and so on and so forth for each result set
   8:   
   9:  using (DbManager db = new DbManager())
  10:  {
  11:      db
  12:          .SetCommand(TestQuery)
  13:          .ExecuteResultSet(sets);
  14:  }

As you can see you have to create an array of MapResultSet objects of the correct size (using a magic value - and you know I don't like them) and then set an instance to each (again using magic values). Seems tedious? I thought so as well. Hence I've done it differently.

First attempt

Most of the time I was creating an array of two MapResutSet objects. So I've written this method that simplified their instantiation:

   1:  public static MapResultSet[] CreateSet<TFirst, TSecond>(
   2:      ref List<TFirst> firstList,
   3:      ref List<TSecond> secondList)
   4:  {
   5:      // make sure lists are initialized
   6:      firstList = firstList ?? new List<TFirst>();
   7:      secondList = secondList ?? new List<TSecond>();
   8:   
   9:      MapResultSet[] sets = new MapResultSet[2];
  10:      sets[0] = new MapResultSet(typeof(TFirst), firstList);
  11:      sets[1] = new MapResultSet(typeof(TSecond), secondList);
  12:   
  13:      return sets;
  14:  }
This worked great until I needed to create three result sets and then four and so on... all the way to seven. So I started scratching my head how to refactor this seemingly very repetitious code and provide some sort of functionality that would allow me to generate an arbitrary long array of MapResultSet objects.

Creating a MapResultSet[] builder

My head was obviously too messed up at the time, so I asked a question on Stackoverflow that brought me the answer I was looking for. These several methods I was using can be refactored by writing a MapResulSet builder. I could write a static class that would do the trick, but since I use repositories and all of them inherit from RepositoryBase class I rather decided to put these builder methods in it so I can use them within all repositories (that are of course already instantiated when used). This is equivalent code to the above example that uses my builder methods:

   1:  List<Parent> parents = null;
   2:  var sets = this
   3:      .InitResultSet()
   4:      .AddResultSet(ref parents)
   5:      .AddResultSet<Child>()
   6:      .AddResultSet<GrandChild>()
   7:      .BuildSet();
   8:   
   9:  using (DbManager db = new DbManager())
  10:  {
  11:      db
  12:          .SetCommand(TestQuery)
  13:          .ExecuteResultSet(sets);
  14:  }

Both code blocks look very similar, but there are advantages in the latter:

  • there are no magic values involved in the code,
  • uses generics and type inference so code is shorter and arguably more readable.
And these two are just from the top of my head. You may come up with additional pros if you have to. As you can see I'm calling my AddResultSet method in two ways. The first one uses the advantages of type inference that is inferred from the variable I'm providing as method parameter. The other two have to provide generic type parameter to work, since I'm calling parameter-less method overloads.

Builder methods - the code

I think I explained the whole situation enough to provide you my builder methods' code that you can reuse in your projects if you want to. So here they are:

   1:  private List<MapResultSet> resultset = null;
   2:   
   3:  /// <summary>
   4:  /// Initializes result set builder.
   5:  /// </summary>
   6:  /// <returns>Returns this repository instance so calls can be chained.</returns>
   7:  internal RespositoryBase InitResultSet()
   8:  {
   9:      resultset = new List<MapResultSet>();
  10:  }
  11:   
  12:  /// <summary>
  13:  /// Adds a new result set list for <see cref="MapResultSet"/> array generation.
  14:  /// </summary>
  15:  /// <typeparam name="T">Result list item type.</typeparam>
  16:  /// <param name="list">Result list.</param>
  17:  /// <returns>Returns this repository instance so calls can be chained.</returns>
  18:  internal RepositoryBase AddResultSet<T>(ref List<T> list)
  19:  {
  20:      list = list ?? new List<T>();
  21:      resultset.Add(new MapResultSet(typeof(T), list));
  22:      return this;
  23:  }
  24:   
  25:  /// <summary>
  26:  /// Adds a new result set list for <see cref="MapResultSet"/> array generation.
  27:  /// </summary>
  28:  /// <typeparam name="T">Result list item type.</typeparam>
  29:  /// <returns>Returns this repository instance so calls can be chained.</returns>
  30:  internal RepositoryBase AddResultSet<T>()
  31:  {
  32:      resultset.Add(new MapResultSet(typeof(T)));
  33:      return this;
  34:  }
  35:   
  36:   
  37:  /// <summary>
  38:  /// Builds the array of <see cref="MapResultSet"/> objects.
  39:  /// </summary>
  40:  /// <returns>Returns an array of <see cref="MapResultSet"/> objects.</returns>
  41:  internal MapResultSet[] BuildResultSet()
  42:  {
  43:      return this.resultset.ToArray();
  44:  }
As you can see there's also InitResultSet method that initializes the list. You can omit this in case you always perform only one data call to the database per repository instantiation. In that case you should just put the new List<MapResultSet>() in the first line and it should work.

Suggest additional functionality to help other developers

But this doesn't stop here. I haven't implemented relational mappings in my builder because I simply don't use them because I'm using DTOs and POCOs and after get lists of DTOs I convert them to POCOs and populate their related entity sets. But if you happen to implement those methods as well, let me know because this code can be even more helpful to others. It may as well happen that I'll need them and I'll implement them, but until that day comes you can write them and send them to me.

That's all folks!

I'm sure you will find this helpful. If not directly with BLToolkit and result set mappings you may refactor your own multiple methods with generic type parameters and reduce your own code duplication.

No comments:

Post a Comment