225

I am new to the Dapper micro ORM. So far I am able to use it for simple ORM related stuff but I am not able to map the database column names with the class properties.

For example, I have the following database table:

Table Name: Person
person_id  int
first_name varchar(50)
last_name  varchar(50)

and I have a class called Person:

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Please note that my column names in the table are different from the property name of the class to which I am trying to map the data which I got from the query result.

var sql = @"select top 1 PersonId,FirstName,LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(sql).ToList();
    return person;
}

The above code won't work as the column names don't match the object's (Person) properties. In this scenario, is there anything i can do in Dapper to manually map (e.g person_id => PersonId) the column names with object properties?

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
user1154985
  • 2,253
  • 3
  • 14
  • 4
  • possible duplicate of [Dapper. Map to SQL Column with spaces in column names](http://stackoverflow.com/questions/14814972/dapper-map-to-sql-column-with-spaces-in-column-names) – David McEleney Jun 30 '15 at 10:43

17 Answers17

232

Dapper now supports custom column to property mappers. It does so through the ITypeMap interface. A CustomPropertyTypeMap class is provided by Dapper that can do most of this work. For example:

Dapper.SqlMapper.SetTypeMap(
    typeof(TModel),
    new CustomPropertyTypeMap(
        typeof(TModel),
        (type, columnName) =>
            type.GetProperties().FirstOrDefault(prop =>
                prop.GetCustomAttributes(false)
                    .OfType<ColumnAttribute>()
                    .Any(attr => attr.Name == columnName))));

And the model:

public class TModel {
    [Column(Name="my_property")]
    public int MyProperty { get; set; }
}

It's important to note that the implementation of CustomPropertyTypeMap requires that the attribute exist and match one of the column names or the property won't be mapped. The DefaultTypeMap class provides the standard functionality and can be leveraged to change this behavior:

public class FallbackTypeMapper : SqlMapper.ITypeMap
{
    private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;

    public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
    {
        _mappers = mappers;
    }

    public SqlMapper.IMemberMap GetMember(string columnName)
    {
        foreach (var mapper in _mappers)
        {
            try
            {
                var result = mapper.GetMember(columnName);
                if (result != null)
                {
                    return result;
                }
            }
            catch (NotImplementedException nix)
            {
            // the CustomPropertyTypeMap only supports a no-args
            // constructor and throws a not implemented exception.
            // to work around that, catch and ignore.
            }
        }
        return null;
    }
    // implement other interface methods similarly

    // required sometime after version 1.13 of dapper
    public ConstructorInfo FindExplicitConstructor()
    {
        return _mappers
            .Select(mapper => mapper.FindExplicitConstructor())
            .FirstOrDefault(result => result != null);
    }
}

And with that in place, it becomes easy to create a custom type mapper that will automatically use the attributes if they're present but will otherwise fall back to standard behavior:

public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
{
    public ColumnAttributeTypeMapper()
        : base(new SqlMapper.ITypeMap[]
            {
                new CustomPropertyTypeMap(
                   typeof(T),
                   (type, columnName) =>
                       type.GetProperties().FirstOrDefault(prop =>
                           prop.GetCustomAttributes(false)
                               .OfType<ColumnAttribute>()
                               .Any(attr => attr.Name == columnName)
                           )
                   ),
                new DefaultTypeMap(typeof(T))
            })
    {
    }
}

That means we can now easily support types that require map using attributes:

Dapper.SqlMapper.SetTypeMap(
    typeof(MyModel),
    new ColumnAttributeTypeMapper<MyModel>());

Here's a Gist to the full source code.

Kaleb Pederson
  • 45,767
  • 19
  • 102
  • 147
  • I've been struggling with this same issue... and this seems like the route i should be going... I'm quite confused as to where this code would get called "Dapper.SqlMapper.SetTypeMap(typeof(MyModel), new ColumnAttributeTypeMapper());" http://stackoverflow.com/questions/14814972/dapper-map-to-sql-column-with-spaces-in-column-names – Rohan Büchner Feb 11 '13 at 23:13
  • You'll want to call it once before you make any queries. You could do it in a static constructor, for example, as it only needs to be called once. – Kaleb Pederson Feb 14 '13 at 00:30
  • 8
    Recommend making this the official answer - this feature of Dapper is extremely useful. – killthrush Apr 17 '13 at 12:58
  • No, it's not included in dapper. I've edited the post to include a link to a gist with the full working code. – Kaleb Pederson Apr 25 '13 at 15:19
  • I'm curious why the fallback support isn't included. My guess would be a concern of degrading the speed. – Karl Kieninger Apr 30 '15 at 16:54
  • 4
    Mapping solution posted by @Oliver (https://stackoverflow.com/a/34856158/364568) works and requires less code. – Ricardo stands with Ukraine Apr 18 '18 at 09:51
  • 33
    I love how the word "easily" is thrown around so effortlessly :P – Jonathan B. Jul 22 '18 at 21:49
126

This works fine:

var sql = @"select top 1 person_id PersonId, first_name FirstName, last_name LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(sql).ToList();
    return person;
}

Dapper has no facility that allows you to specify a Column Attribute, I am not against adding support for it, providing we do not pull in the dependency.

Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • @Sam Saffron is there any way I can specify the table alias. I have a class named Country but in the db the table has very convoluted name due to archic naming conventions. – TheVillageIdiot Aug 07 '12 at 14:55
  • 85
    Column Attribue would be handy for mapping stored procedure results. – Ronnie Overby Nov 02 '12 at 17:48
  • 2
    Column attributes would also be useful for more easily facilitating tight physical and/or semantic coupling between your domain and the tool implementation details you're using to materialize your entities. Therefore, don't add support for this!!!! :) – Derek Greer Jun 26 '14 at 15:43
  • 2
    I don’t get why columnattribe isn’t there when tableattribute. How would this example work with inserts, updates, and SPs? I would like to see columnattribe, its dead simple and would make life very easy migrating from other solutions that implement something similar like the now defunct linq-sql. – Vman May 10 '20 at 20:21
115

For some time, the following should work:

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 9
    Although this is not really the answer to the question to "*Manually* Map column names with class properties", for me it's much better than having to manually map (unfortunately in PostgreSQL it's better to use underscores in column names). Please don't remove the MatchNamesWithUnderscores option in the next versions! Thank you!!! – victorvartan Aug 10 '16 at 20:53
  • 5
    @victorvartan there are no plans to remove the `MatchNamesWithUnderscores` option. **At best**, if we refactored the configuration API, I would leave the `MatchNamesWithUnderscores` member in place (that still works, ideally) and add an `[Obsolete]` marker to point people to the new API. – Marc Gravell Aug 11 '16 at 09:49
  • 4
    @MarcGravell the words "For some time" at the beginning of your answer got me worried that you might remove it in a future version, thanks for clarifying! And a big thank you for Dapper, a wonderful micro ORM that I just started using for a tiny project along with Npgsql on ASP.NET Core! – victorvartan Aug 11 '16 at 12:01
  • 2
    This is easily the best answer. I've found piles and piles of work arounds, but finally stumbled upon this. Easily the best but least-advertised answer. – teaMonkeyFruit Nov 26 '19 at 07:58
  • Doesn't match a property named _500 when the column name is 500 – PandaWood May 13 '22 at 13:45
  • If I had to pay you for all the times over the years your answers have helped me I'd be a poor man.. – WBuck Apr 07 '23 at 01:25
64

I do the following using dynamic and LINQ:

    var sql = @"select top 1 person_id, first_name, last_name from Person";
    using (var conn = ConnectionFactory.GetConnection())
    {
        List<Person> person = conn.Query<dynamic>(sql)
                                  .Select(item => new Person()
                                  {
                                      PersonId = item.person_id,
                                      FirstName = item.first_name,
                                      LastName = item.last_name
                                  }
                                  .ToList();

        return person;
    }
Jeson Martajaya
  • 6,996
  • 7
  • 54
  • 56
liorafar
  • 2,264
  • 4
  • 19
  • 39
32

Here is a simple solution that doesn't require attributes allowing you to keep infrastructure code out of your POCOs.

This is a class to deal with the mappings. A dictionary would work if you mapped all the columns, but this class allows you to specify just the differences. In addition, it includes reverse maps so you can get the field from the column and the column from the field, which can be useful when doing things such as generating sql statements.

public class ColumnMap
{
    private readonly Dictionary<string, string> forward = new Dictionary<string, string>();
    private readonly Dictionary<string, string> reverse = new Dictionary<string, string>();

    public void Add(string t1, string t2)
    {
        forward.Add(t1, t2);
        reverse.Add(t2, t1);
    }

    public string this[string index]
    {
        get
        {
            // Check for a custom column map.
            if (forward.ContainsKey(index))
                return forward[index];
            if (reverse.ContainsKey(index))
                return reverse[index];

            // If no custom mapping exists, return the value passed in.
            return index;
        }
    }
}

Setup the ColumnMap object and tell Dapper to use the mapping.

var columnMap = new ColumnMap();
columnMap.Add("Field1", "Column1");
columnMap.Add("Field2", "Column2");
columnMap.Add("Field3", "Column3");

SqlMapper.SetTypeMap(typeof (MyClass), new CustomPropertyTypeMap(typeof (MyClass), (type, columnName) => type.GetProperty(columnMap[columnName])));
Randall Sutton
  • 1,875
  • 19
  • 27
  • 1
    This is a good solution when you basically have a mismatch of properties in your POCO to what your database is returning from, for example, a stored procedure. – crush Sep 26 '14 at 18:28
  • 1
    I kinda like the conciseness that using an attribute gives, but conceptually this method is cleaner - it doesn't couple your POCO to database details. – Bruno Brant Aug 24 '18 at 15:11
  • 1
    If I understand Dapper correctly, it doesn't have a specific Insert() method, just an Execute()... would this mapping approach work for insertions? Or updates? Thanks – StayOnTarget Sep 26 '18 at 19:19
22

An easy way to achieve this is to just use aliases on the columns in your query.

If your database column is PERSON_ID and your object's property is ID, you can just do

select PERSON_ID as Id ...

in your query and Dapper will pick it up as expected.

Pang
  • 9,564
  • 146
  • 81
  • 122
Brad Westness
  • 1,532
  • 13
  • 18
22

Taken from the Dapper Tests which is currently on Dapper 1.42.

// custom mapping
var map = new CustomPropertyTypeMap(
                 typeof(TypeWithMapping), 
                 (type, columnName) => 
                        type.GetProperties().FirstOrDefault(prop => 
                                GetDescriptionFromAttribute(prop) == columnName));
Dapper.SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);

Helper class to get name off the Description attribute (I personally have used Column like @kalebs example)

static string GetDescriptionFromAttribute(MemberInfo member)
{
   if (member == null) return null;

   var attrib = (DescriptionAttribute) Attribute.GetCustomAttribute(
                         member,
                         typeof(DescriptionAttribute), false);

   return attrib == null ? null : attrib.Description;
}

Class

public class TypeWithMapping
{
   [Description("B")]
   public string A { get; set; }

   [Description("A")]
   public string B { get; set; }
}
surfmuggle
  • 5,527
  • 7
  • 48
  • 77
Oliver
  • 35,233
  • 12
  • 66
  • 78
  • 5
    In order for it to work even for properties where no description is defined, I changed the return of `GetDescriptionFromAttribute` to `return (attrib?.Description ?? member.Name).ToLower();` and added `.ToLower()` to `columnName`in the map it shouldn't be case sensitive. – Sam White Nov 29 '18 at 21:13
  • 2
    Thank you. Is there a way to set the mapping per SQL call instead of globally? I only need it to use the on half of my calls. – Lukas Jan 19 '21 at 15:27
18

Before you open the connection to your database, execute this piece of code for each of your poco classes:

// Section
SqlMapper.SetTypeMap(typeof(Section), new CustomPropertyTypeMap(
    typeof(Section), (type, columnName) => type.GetProperties().FirstOrDefault(prop =>
    prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));

Then add the data annotations to your poco classes like this:

public class Section
{
    [Column("db_column_name1")] // Side note: if you create aliases, then they would match this.
    public int Id { get; set; }
    [Column("db_column_name2")]
    public string Title { get; set; }
}

After that, you are all set. Just make a query call, something like:

using (var sqlConnection = new SqlConnection("your_connection_string"))
{
    var sqlStatement = "SELECT " +
                "db_column_name1, " +
                "db_column_name2 " +
                "FROM your_table";

    return sqlConnection.Query<Section>(sqlStatement).AsList();
}
tedi
  • 6,350
  • 5
  • 52
  • 67
  • 3
    It needs all properties to have Column attribute. Is there any way to map with property in case mapper is not available? – sandeep.gosavi May 14 '18 at 05:16
  • @sandeep.gosavi check out my answer here https://stackoverflow.com/questions/14814972/dapper-map-to-sql-column-with-spaces-in-column-names/74291093#74291093, it does not need the mapper – DonMiguelSanchez Nov 10 '22 at 17:15
15

Messing with mapping is borderline moving into real ORM land. Instead of fighting with it and keeping Dapper in its true simple (fast) form, just modify your SQL slightly like so:

var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";
mxmissile
  • 11,464
  • 3
  • 53
  • 79
  • I can not see a difference to this answer from Brad: https://stackoverflow.com/a/30200384/819887 Is there a difference? – surfmuggle Aug 27 '23 at 15:04
7

If you're using .NET 4.5.1 or higher checkout Dapper.FluentColumnMapping for mapping the LINQ style. It lets you fully separate the db mapping from your model (no need for annotations)

mamuesstack
  • 1,111
  • 2
  • 16
  • 34
  • 6
    I am the author of Dapper.FluentColumnMapping. Separating the mappings from the models was one of the primary design goals. I wanted to be to isolate the core data access (i.e. repository interfaces, model objects, etc...) from the database-specific concrete implementations for a clean separation of concerns. Thanks for the mention and I'm glad you found it useful! :-) – Alexander Jun 02 '17 at 21:36
  • https://github.com/henkmollema/Dapper-FluentMap is similar. But you don't need an 3rd party package anymore. Dapper added Dapper.SqlMapper. See my answer for more details if you are interested. – tedi Aug 02 '17 at 08:56
6

This is piggy backing off of other answers. It's just a thought I had for managing the query strings.

Person.cs

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public static string Select() 
    {
        return $"select top 1 person_id {nameof(PersonId)}, first_name {nameof(FirstName)}, last_name {nameof(LastName)}from Person";
    }
}

API Method

using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(Person.Select()).ToList();
    return person;
}
christo8989
  • 6,442
  • 5
  • 37
  • 43
3

The simple solution to the problem Kaleb is trying to solve is just to accept the property name if the column attribute doesn't exist:

Dapper.SqlMapper.SetTypeMap(
    typeof(T),
    new Dapper.CustomPropertyTypeMap(
        typeof(T),
        (type, columnName) =>
            type.GetProperties().FirstOrDefault(prop =>
                prop.GetCustomAttributes(false)
                    .OfType<ColumnAttribute>()
                    .Any(attr => attr.Name == columnName) || prop.Name == columnName)));

2

The easier way (same as @Matt M's answer but corrected and added fallback to default map)

// override TypeMapProvider to return custom map for every requested type
Dapper.SqlMapper.TypeMapProvider = type =>
   {
       // create fallback default type map
       var fallback = new DefaultTypeMap(type);
       return new CustomPropertyTypeMap(type, (t, column) =>
       {
           var property = t.GetProperties().FirstOrDefault(prop =>
               prop.GetCustomAttributes(typeof(ColumnAttribute))
                   .Cast<ColumnAttribute>()
                   .Any(attr => attr.Name == column));

           // if no property matched - fall back to default type map
           if (property == null)
           {
               property = fallback.GetMember(column)?.Property;
           }

           return property;
       });
   };
CEPOCTb
  • 21
  • 1
1

for all of you who use Dapper 1.12, Here's what you need to do to get this done:

  • Add a new column attribute class:
      [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]
    
      public class ColumnAttribute : Attribute
      {
    
        public string Name { get; set; }
    
        public ColumnAttribute(string name)
        {
          this.Name = name;
        }
      }
    

  • Search for this line:
    map = new DefaultTypeMap(type);
    

    and comment it out.

  • Write this instead:
            map = new CustomPropertyTypeMap(type, (t, columnName) =>
            {
              PropertyInfo pi = t.GetProperties().FirstOrDefault(prop =>
                                prop.GetCustomAttributes(false)
                                    .OfType<ColumnAttribute>()
                                    .Any(attr => attr.Name == columnName));
    
              return pi != null ? pi : t.GetProperties().FirstOrDefault(prop => prop.Name == columnName);
            });
    

  • Ben Collins
    • 20,538
    • 18
    • 127
    • 187
    Uri Abramson
    • 6,005
    • 6
    • 40
    • 62
    • I'm not sure I understand - are you recommending that users change Dapper to make attribute mapping by columns possible? If so, it's possible using the code I posted above without making changes to Dapper. – Kaleb Pederson Apr 25 '13 at 15:22
    • 1
      But then you'll have to call the mapping function for each and every one of your Model Types won't you?? i'm interested in a generic solution so that all of my types could use the attribute without having to call the mapping for each type. – Uri Abramson Apr 25 '13 at 15:45
    • 2
      I would like to see DefaultTypeMap be implemented using a strategy pattern such that it can be replaced for the reason @UriAbramson mentions. See https://code.google.com/p/dapper-dot-net/issues/detail?id=140 – Richard Collette Jun 11 '13 at 16:05
    1

    Kaleb Pederson's solution worked for me. I updated the ColumnAttributeTypeMapper to allow a custom attribute (had requirement for two different mappings on same domain object) and updated properties to allow private setters in cases where a field needed to be derived and the types differed.

    public class ColumnAttributeTypeMapper<T,A> : FallbackTypeMapper where A : ColumnAttribute
    {
        public ColumnAttributeTypeMapper()
            : base(new SqlMapper.ITypeMap[]
                {
                    new CustomPropertyTypeMap(
                       typeof(T),
                       (type, columnName) =>
                           type.GetProperties( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(prop =>
                               prop.GetCustomAttributes(true)
                                   .OfType<A>()
                                   .Any(attr => attr.Name == columnName)
                               )
                       ),
                    new DefaultTypeMap(typeof(T))
                })
        {
            //
        }
    }
    
    Pang
    • 9,564
    • 146
    • 81
    • 122
    GameSalutes
    • 1,762
    • 2
    • 18
    • 24
    1

    I know this is a relatively old thread, but I thought I'd throw what I did out there.

    I wanted attribute-mapping to work globally. Either you match the property name (aka default) or you match a column attribute on the class property. I also didn't want to have to set this up for every single class I was mapping to. As such, I created a DapperStart class that I invoke on app start:

    public static class DapperStart
    {
        public static void Bootstrap()
        {
            Dapper.SqlMapper.TypeMapProvider = type =>
            {
                return new CustomPropertyTypeMap(typeof(CreateChatRequestResponse),
                    (t, columnName) => t.GetProperties().FirstOrDefault(prop =>
                        {
                            return prop.Name == columnName || prop.GetCustomAttributes(false).OfType<ColumnAttribute>()
                                       .Any(attr => attr.Name == columnName);
                        }
                    ));
            };
        }
    }
    

    Pretty simple. Not sure what issues I'll run into yet as I just wrote this, but it works.

    Matt M
    • 1,093
    • 2
    • 11
    • 26
    • What does CreateChatRequestResponse look like? Also, how are you invoking it in the startup? – Glen F. Feb 15 '19 at 16:08
    • 2
      @GlenF. the point is that it doesnt matter what CreateChatRequestResponse looks like. it can be any POCO. this gets invoked in your startup. You can just invoke it on your app start either in your StartUp.cs or your Global.asax. – Matt M Feb 22 '19 at 15:56
    • 2
      Perhaps I am completely wrong, but unless `CreateChatRequestResponse` is replaced by `T` how would this iterate through all Entity objects. Please correct me if I am wrong. – Fawad Raza Oct 31 '19 at 20:51
    0

    I would suggest solution similar to @liorafar's, but based on dictionaries rather than on dynamics:

    using var conn = ConnectionFactory.GetConnection();
    var person = conn.Query(sql)
        .Cast<IDictionary<string, object>>()
        .Select(record =>
            new Person
            {
                PersonId = (int)record["person_id"],
                FirstName = (string)record["first_name"],
                LastName = (string)record["last_name"],
            })
        .ToList();
    

    In my opinion, this option is friendlier for refactoring: e.g. you can declare column names as constants or read them from configuration. Additionally, unlike solution with dynamics, it allows to extract method of transforming the dictionary to model instance (instance of Person type) to separate method, which is especially useful for models with many fields.