-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Description
Background
I love Dapper because it adds great support for hydrating immutable objects from a database. For example, it allows us to hydrate objects of the following type like so:
public class Example {
public Guid Id { get; }
public String Name { get; }
public DateTime Created { get; }
public Guid CreatedBy { get; }
public DateTime Updated { get; }
public Guid UpdatedBy { get; }
public Example(
Guid id,
String name,
DateTime created,
Guid createdBy,
DateTime updated,
Guid updatedBy) {
this.Id = id;
this.Name = name;
this.Created = created;
this.CreatedBy = createdBy;
this.Updated = updated;
this.UpdatedBy = updatedBy;
}
}
public IEnumerable<Example> GetExamples() {
var columns = String.Join(
","
typeof(Example)
.GetTypeInfo()
.GetProperties()
.Select(property => $"`{property.Name}`")
);
return this.connection.Query<Example>($"SELECT {columns} FROM ExampleTable");
}Problem
Unfortunately, the columns provided to the query parameters must be in the same order that the constructor parameters are in. So, given the above example, if I changed the Example to look like this...
public class Example {
public Guid Id { get; }
public String Name { get; }
/* [ ... the rest removed for emphasis ... ] */
public Example(
String name,
Guid id,
/* [ ... the rest removed for emphasis ... ] */) {
}
}... it will fail. Here is where I'm not 100% because I have a hard time reading the IL, but my understanding is that first it will fail because the DefaultTypeMap won't find the constructor because the names are in a different order. You can get around this by making your own SqlMapper.ITypeMap implementation that does not care about the order of the SQL query values matching the order of the constructor. However, this just makes the code fail (via, at least in .NET core, with weird NullReferenceExceptions) later because the SqlMapper code enumerates on the constructor parameters with the assumption they are in the same order as the SQL query.
Suggestion
Update the SqlMapper logic here such that it does not enumerable over the ConstructorParameter objects assuming they are in the same order from which they were returned from the query.
Workaround
The workaround for this is to fetch the SqlMapper.ITypeMap implementation Dapper has plugged in for the type you're about to query via this method. From there you can fetch the ConstructorInfo being used, if one happens to be used. This has the following negative consequences:
- You have to reproduce the ConstructorInfo processing code which involves Reflection, and you have to call it every time in case the
SqlMapper.ITypeMapimplementation for the objects you are hydrating changed (and even then there's a race condition). - You have to add branching to your code if you have a mix of immutable and mutable objects that you hydrate to account for objects which have property setters instead of constructor arguments.