This is a follow on to a previous post about customising naming conventions for PostgreSQL and EF Core. In this post I describe one way to use snake case naming conventions when using Dapper, rather than EF Core, by using "Schema" utility classes, the nameof()
operator, and a ToSnakeCase()
extension method.
PostgreSQL and naming conventions
If you're coming from SQL Server, PostgreSQL can seem very pedantic about column names. In SQL Server, case sensitivity doesn't matter for column names, so if a column is named FirstName
, then firstName
, firstname
, or even FIRSTNAME
are all valid. Unfortunately, the only way to query that column in PostgreSQL, is using "FirstName"
(including the quotes). Using quotes like this can get tiresome, so it's a common convention to use "snake_case" for columns and tables; that is, all-lowercase with _
to separate words e.g. first_name
.
If you'd like a bit more background, or you're working with EF Core, I discuss this in greater depth in my previous post.
In the previous post, I described how you can customise EF Core's naming conventions to use snake_case. This ensures all tables, columns, and indexes are generated using snake_case, and that they map correctly to the EF Core entities. To do so, I created a simple ToSnakeCase()
extension method that uses a regex to convert "camelCase" strings to "snake_case".
public static class StringExtensions
{
public static string ToSnakeCase(this string input)
{
if (string.IsNullOrEmpty(input)) { return input; }
var startUnderscores = Regex.Match(input, @"^_+");
return startUnderscores + Regex.Replace(input, @"([a-z0-9])([A-Z])", "$1_$2").ToLower();
}
}
One of the comments on that post from Alex was interested in how to use this method to achieve the same result for Dapper commands:
I'm using Dapper in many parts of my application and i used to name my table in queryies using
nameof()
, for example:$"SELECT id FROM {nameof(EntityName)}"
. That way, i could rename entitie's names without replacing each sql query ...
So the naive approach will be to replace it with"SELECT id FROM {nameof(EntityName).ToSnakeCase()}"
but, each time the query is "build", theSnakeCase
(and the regexp) will be processed, so it'll not be very good in term of performance. Did you know a better approach to this problem ?
Using the nameof()
operator with Dapper
Dapper is a micro-ORM that provides various features for querying a database and mapping the results to C# objects. It's not as feature rich as something like EF Core, but it's much more lightweight, and so usually a lot faster. It uses a fundamentally different paradigm to most ORMs: EF Core lets you interact with a database without needing to know any SQL, whereas you use Dapper by writing hand-crafted SQL queries in your app.
Dapper provides a number of extension methods on IDbConnection
that serve as the API surface. So say you wanted to query the details of a user with id=123
from a table in PostgreSQL. You could use something like the following:
IDbConnection connection; // get a connection instance from somewhere
var sql = "SELECT id, first_name, last_name, email FROM users WHERE id = @id";
var user = connection.Query<User>(sql, new { id = 123}).SingleOrDefault();
The ability to control exactly what SQL code runs on your database can be extremely useful, especially for performance sensitive code. However there are some obvious disadvantages when compared to a more fully featured ORM like EF Core.
One of the most obvious disadvantages is the possibility for typos in your SQL code. You could have typos in your column and table names, or you could have used the wrong syntax. That's largely just the price you pay for this sort of "lower-level" access, but there's a couple of things you can do to reduce the problem.
A common approach, as described in Alex's comment is to use string interpolation and the nameof()
operator to inject a bit of type safety into your SQL statements. This works well when the column and tables names of your database correspond to property and class names in your program.
For example, imagine you have the following User
type:
public class User
{
public int Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
If your column names match the property names of User
(for example property Id
corresponds to column name Id
), then you could query the database using the following:
var id = 123;
var sql = $@"
SELECT {nameof(User.Id)}, {nameof(User.FirstName)}, {nameof(User.LastName)}
FROM {nameof(User)}
WHERE {nameof(User.Id)} = @{nameof(id)}";
var user = connection.Query<User>(sql, new { id }).SingleOrDefault();
That all works well as long as everything matches up between your classes and your database schema. But I started off this post by describing snake_case as a common convention of PostgreSQL. Unless you also name your C# properties and classes using snake_case (please don't) you'll need to use a different approach.
Using static schema classes to avoid typos
As Alex described in his comment, you could just call ToSnakeCase()
inline when building up your queries:
var id = 123;
var sql = $@"
SELECT {nameof(User.Id).ToSnakeCase()},
{nameof(User.FirstName).ToSnakeCase()},
{nameof(User.LastName).ToSnakeCase()}
FROM {nameof(User).ToSnakeCase()}
WHERE {nameof(User.Id).ToSnakeCase()} = @{nameof(id)}";
var user = connection.Query<User>(sql, new { id }).SingleOrDefault();
Unfortunately, calling a regex for every column in every query is pretty wasteful and unnecessary. Instead, I often like to create "schema" classes that just define the column and table names in a central location, reducing the opportunity for typos:
public static class UserSchema
{
public static readonly Table { get; } = "user";
public static class Columns
{
public static string Id { get; } = "id";
public static string Email { get; } = "email";
public static string FirstName { get; } = "first_name";
public static string LastName { get; } = "last_name";
}
}
Each property of the User
class has a corresponding getter-only static
property in the UserSchema.Columns
class that contains the associated column name. You can then use this schema class in your Dapper SQL queries without performance issues:
var id = 123;
var sql = $@"
SELECT {UserSchema.Columns.Id)},
{UserSchema.Columns.FirstName},
{UserSchema.Columns.LastName}
FROM {UserSchema.Table}
WHERE {UserSchema.Columns.Id} = @{nameof(id)}";
var user = connection.Query<User>(sql, new { id }).SingleOrDefault();
I've kind of dodged the question at this point - Alex was specifically looking for a way to avoid having to hard code the strings "first_name"
, "last_name"
etc; all I've done is put them in a central location. But we can use this first step to achieve the end goal, by simply replacing those hard-coded strings with their nameof().ToSnakeCase()
equivalents:
public static class UserSchema
{
public static readonly Table { get; } = nameof(User).ToSnakeCase();
public static class Columns
{
public static string Id { get; } = nameof(User.Id).ToSnakeCase();
public static string Email { get; } = nameof(User.Email).ToSnakeCase();
public static string FirstName { get; } = nameof(User.FirstName).ToSnakeCase();
public static string LastName { get; } = nameof(User.LastName).ToSnakeCase();
}
}
Because we used getter-only properties with an initialiser , the nameof().ToSnakeCase()
expression is only executed once per column. No matter how many times you use the UserSchema.Columns.Id
property in your SQL queries, you only take the regular expression hit once.
Personally, I feel like this strikes a good balance between convenience, performance, and safety. Clearly creating the *Schema
tables involves some duplication compared to using hard-coded column names, but I like the strongly-typed feel to the SQL queries using this approach. And when your column and class names don't match directly, it provides a clear advantage over trying to use the User
class directly with nameof()
.
Configuring Dapper to map snake_case results
The schema classes shown here are only one part of the solution to using snake_case column names with Dapper. The *Schema
approach helps avoid typos in your SQL queries, but it doesn't help mapping the query results back to your objects.
By default, Dapper expects the columns returned by a query to match the property names of the type you're mapping to. For our User
example, that means Dapper expects a column named FirstName
, but the actual column name is first_name
. Luckily, fixing this is a simple one-liner:
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
With this statement added to your application, you'll be able to query your PostgreSQL snake_case columns using the *Schema
classes, and map them to your POCO classes.
Summary
This post was in response to a comment I received about using snake_case naming conventions with Dapper. The approach I often use to avoid typos in my SQL queries is to create static "schema" classes, that describe the shape of my tables. These classes can then be used in SQL queries with interpolated strings. The properties of the schema classes can use convenience methods such as nameof()
and ToSnakeCase()
as they are only executed once, instead of on every reference to a column.
If you're using this approach, don't forget to set Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true
so you can map your query objects back to your POCO classes!