Skip to content

Add support for order-agnostic constructor parameter SqlMapper.ITypeMap implementations #752

@carusology

Description

@carusology

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:

  1. You have to reproduce the ConstructorInfo processing code which involves Reflection, and you have to call it every time in case the SqlMapper.ITypeMap implementation for the objects you are hydrating changed (and even then there's a race condition).
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions