In this edition, we build a class that can generate relatively-complicated SQL WHERE clauses from a group of parameters.
Plus: you should deploy more; feature flags; designing better APIs; cancellation; and VS's 25th anniversary!
Dapper Where Clause Builder
My team and I made the commitment to use Dapper entirely for our major project, and so far it's going pretty well. But occasionally we are running across things like enormously complicated queries, which Entity Framework can do fairly simply but Dapper has a harder time with.
Queries such as this, from one of our pages that has a bunch of search fields:
Notice that this particular query has to do a null check, and if the property in question is null, the item gets included in the result set.
This is a terribly complicated query at first glance, but peer a little closer and you start to see the similarities. I decided that it might be possible to create a class which generates the above code as a SQL WHERE clause, and parameters, for Dapper to consumer. Turns out, it IS possible.
In this post and the next (which will come out in two weeks, see below), we'll show how to build the DapperWhereClauseBuilder class for a few types of queries, and how to use it to replace the above query. Let's go!
The Internals of DapperWhereClauseBuilder
The DapperWhereClauseBuilder class needs to do the following things:
Accept a string parameter name and a value for any given parameter.
Accept "is not null" parameters.
Accept "is in this collection" parameters.
Generate the WHERE clause that results from all the given parameters.
We're going to do 1 and part of 4 in this post, and we'll do the others in the next one.
So, let's start with a new DapperWhereClauseBuilder class and an internal collection of parameters, which we will make a SortedDictionary<string, object> type:
public class DapperWhereClauseBuilder
{
private readonly SortedDictionary<string, object> singleValues = new SortedDictionary<string, object>();
}
We now need accessor methods to add parameters of different types to the where clause builder. Let's start with a simple one, for int:
public class DapperWhereClauseBuilder
{
private readonly SortedDictionary<string, object> singleValues = new SortedDictionary<string, object>();
public DapperWhereClauseBuilder AddValue(string parameterName, int? value)
{
if (value.HasValue && !singleValues.ContainsKey(parameterName))
singleValues.Add(parameterName, value.Value);
return this;
}
}
Note that we're doing two kinds of checks on the passed-in value: confirming that the nullable value actually has a value, and confirming that the value parameterName does not already exist in our dictionary of parameters. We also made the method return DapperWhereClauseBuilder so it can be used in a fluent manner.
We will need quite a few of this basic AddValue() methods; here's two more:
public class DapperWhereClauseBuilder
{
//...Rest of implementation
public DapperWhereClauseBuilder AddValue(string parameterName, double? value)
{
if (value.HasValue && !singleValues.ContainsKey(parameterName))
singleValues.Add(parameterName, value.Value);
return this;
}
public DapperWhereClauseBuilder AddValue(string parameterName, long? value)
{
if (value.HasValue && !singleValues.ContainsKey(parameterName))
singleValues.Add(parameterName, value.Value);
return this;
}
}
For the majority of these methods, the logic is the same even as the type of the second parameter changes: ensure that the value is not null, and ensure that the parameterName does not already exist in the dictionary. The implementation of that logic changes, very slightly, when we get to adding a value of type string:
This article is for paying subscribers only
Sign up now and upgrade your account to read the article and get access to the full library of articles for paying subscribers only.