DapperLib / DapperLib/Dapper

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

Open
#752 11 comments 6 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C#
Stars
18.4k
Forks
3.7k
Avg merge
5h 8m
Merged PRs (30d)
1

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:

  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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read constructor matching in Dapper/DefaultTypeMap.cs and the materialization logic around SqlMapper.cs line 3007, then inspect the type-map lookup around line 2798. Verify how a custom SqlMapper.ITypeMap supplies constructor parameters and define completion as supporting differing SQL-column and constructor-parameter orders without the reported later failure.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, sql
Domain
backend, database
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.