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;
4: sets = new MapResultSet(typeof(Parent), parents);
5: sets = new MapResultSet(typeof(Child));
6: sets = new MapResultSet(typeof(Grandchild));
7: // and so on and so forth for each result set
9: using (DbManager db = new DbManager())
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.
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)
5: // make sure lists are initialized
6: firstList = firstList ?? new List<TFirst>();
7: secondList = secondList ?? new List<TSecond>();
9: MapResultSet sets = new MapResultSet;
10: sets = new MapResultSet(typeof(TFirst), firstList);
11: sets = new MapResultSet(typeof(TSecond), secondList);
13: return sets;
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
4: .AddResultSet(ref parents)
9: using (DbManager db = new DbManager())
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.
AddResultSetmethod 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;
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()
9: resultset = new List<MapResultSet>();
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)
20: list = list ?? new List<T>();
21: resultset.Add(new MapResultSet(typeof(T), list));
22: return this;
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>()
32: resultset.Add(new MapResultSet(typeof(T)));
33: return this;
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()
43: return this.resultset.ToArray();
InitResultSetmethod 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.