Dapper with attribute mapping

I am trying to map my Id fields to the attributes of a column, but for some reason this does not work, and I cannot understand why. I created a test project to demonstrate what I'm trying.

Firstly, I got 2 objects:

Entity Table1

using System.Data.Linq.Mapping; namespace DapperTestProj { public class Table1 { [Column(Name = "Table1Id")] public int Id { get; set; } public string Column1 { get; set; } public string Column2 { get; set; } public Table2 Table2 { get; set; } public Table1() { Table2 = new Table2(); } } } 

and the essence of Table2

 using System.Data.Linq.Mapping; namespace DapperTestProj { public class Table2 { [Column(Name = "Table2Id")] public int Id { get; set; } public string Column3 { get; set; } public string Column4 { get; set; } } } 

In my database, I got 2 tables, also called Table1 and Table2. Both tables got their columns with a name equal to the entity, except that Table1 has a column called Table2Id, and there is also a foreign key between Table1.Table2Id and Table2.Id.

In addition, both tables have 1 record, and they have both identifiers.

What I'm trying to do is execute a query with dapper and it should return an object of type Table1. This works, but the properties of Table1.Id and Table1.Table2.Id remain 0 (the default is an integer). I expect the column attributes to display Id fields, but this is clearly not the case.

This is the query and mapping performed in code:

 private Table1 TestMethod(IDbConnection connection) { var result = connection.Query<Table1, Table2, Table1>( @"SELECT T1.Id as Table1Id, T1.Column1 as Column1, T1.Column2 as Column2, T2.Id as Table2Id, T2.Column3 as Column3, T2.Column4 as Column4 FROM Table1 T1 INNER JOIN Table2 T2 ON T1.Table2Id = T2.Id", (table1, table2) => { table1.Table2 = table2; return table1; }, splitOn: "Table2Id" ).SingleOrDefault(); return result; } 

Now I could rename both properties of the Id property in essence to Table1Id and Table2Id, but instead I prefer the identifier instead of the more logical code, like Table1.Id instead of Table1.Table1Id. So I was wondering if it is possible that I want here, and if so, how?

Edit:

I found this topic: Manually match column names with class properties

And with the code in the first Kaleb Pederson post, you can use attributes when necessary, with the FallBackTypeMapper class and the ColumnAttributeTypeMapper class. All that is needed is to add the necessary classes to typemapping with:

 SqlMapper.SetTypeMap(typeof(Table1), new ColumnAttributeTypeMapper<Table1>()); SqlMapper.SetTypeMap(typeof(Table2), new ColumnAttributeTypeMapper<Table2>()); 

But with many entities, this list will grow for a long time. You also needed to add each class manually to the list, and I was wondering if this could be done automatically with a more general one with Reflection. I found a piece of code able to get all types:

  const string @namespace = "DapperTestProj.Entities"; var types = from type in Assembly.GetExecutingAssembly().GetTypes() where type.IsClass && type.Namespace == @namespace select type; 

And fixated on all types, I can do this, only the problem that I have now is what piece of code do I need, or should be placed where the question marks are now?

  typeList.ToList().ForEach(type => SqlMapper.SetTypeMap(type, new ColumnAttributeTypeMapper</*???*/>())); 

Edit:

After more searching, I found a solution for my last problem:

  typeList.ToList().ForEach(type => { var mapper = (SqlMapper.ITypeMap)Activator.CreateInstance( typeof(ColumnAttributeTypeMapper<>) .MakeGenericType(type)); SqlMapper.SetTypeMap(type, mapper); }); 
+14
c # dapper system.reflection
source share
3 answers

To complete the solution, I want to share the code that I found and put it together with those who are interested.

Instead of (ab) using System.Data.Linq.Mapping.ColumnAttribute there may be more logic (and possibly saving, although the likelihood that Microsoft will change linq to sql ColumnAttribute will be very small to create its own ColumnAttribute class:

ColumnAttribute.cs

 using System; namespace DapperTestProj.DapperAttributeMapper //Maybe a better namespace here { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class ColumnAttribute : Attribute { public string Name { get; set; } public ColumnAttribute(string name) { Name = name; } } } 

Being in the topic that I mentioned earlier, the FallBackTypeMapper and ColumnAttributeTypeMapper classes:

FallBackTypeMapper.cs

 using System; using System.Collections.Generic; using System.Reflection; using Dapper; namespace DapperTestProj.DapperAttributeMapper { public class FallBackTypeMapper : SqlMapper.ITypeMap { private readonly IEnumerable<SqlMapper.ITypeMap> _mappers; public FallBackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers) { _mappers = mappers; } public ConstructorInfo FindConstructor(string[] names, Type[] types) { foreach (var mapper in _mappers) { try { var result = mapper.FindConstructor(names, types); 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; } public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) { foreach (var mapper in _mappers) { try { var result = mapper.GetConstructorParameter(constructor, 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; } 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; } } } 

ColumnAttributeTypeMapper.cs

 using System.Linq; using Dapper; namespace DapperTestProj.DapperAttributeMapper { 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(attribute => attribute.Name == columnName) ) ), new DefaultTypeMap(typeof(T)) }) { } } } 

and finally, TypeMapper.cs to initialize the mapping.

 using System; using System.Linq; using System.Reflection; using Dapper; namespace DapperTestProj.DapperAttributeMapper { public static class TypeMapper { public static void Initialize(string @namespace) { var types = from assem in AppDomain.CurrentDomain.GetAssemblies().ToList() from type in assem.GetTypes() where type.IsClass && type.Namespace == @namespace select type; types.ToList().ForEach(type => { var mapper = (SqlMapper.ITypeMap)Activator .CreateInstance(typeof(ColumnAttributeTypeMapper<>) .MakeGenericType(type)); SqlMapper.SetTypeMap(type, mapper); }); } } } 

At startup, you must call TypeMapper.Initialize:

 TypeMapper.Initialize("DapperTestProj.Entities"); 

And you can start using attributes for object properties

 using DapperTestProj.DapperAttributeMapper; namespace DapperTestProj.Entities { public class Table1 { [Column("Table1Id")] public int Id { get; set; } public string Column1 { get; set; } public string Column2 { get; set; } public Table2 Table2 { get; set; } public Table1() { Table2 = new Table2(); } } } 
+20
source share

Cornelis's answer is correct, however I wanted to add an update to it. Starting with the current version of Dapper, you also need to implement SqlMapper.ItypeMap.FindExplicitConstructor() . I am not sure when this change was made, but it is for everyone who stumbles upon this issue and lacks that part of the solution.

Inside FallbackTypeMapper.cs

 public ConstructorInfo FindExplicitConstructor() { return _mappers.Select(m => m.FindExplicitConstructor()) .FirstOrDefault(result => result != null); } 

You can also use the ColumnAttribute class located in the System.ComponentModel.DataAnnotations.Schema namespace, instead of collapsing your own for an embedded version other than the / orm database.

+3
source share

it gets even better

 public class ColumnOrForeignKeyAttributeTypeMapper<T> : FallBackTypeMapper { public ColumnOrForeignKeyAttributeTypeMapper() : base(new SqlMapper.ITypeMap[] { new CustomPropertyTypeMap(typeof(T), (type, columnName) => type.GetProperties().FirstOrDefault(prop => prop.GetCustomAttributes(false) .Where(a=>a is ColumnAttribute || a is ForeignKeyAttribute) .Any(attribute => attribute.GetType() == typeof(ColumnAttribute) ? ((ColumnAttribute)attribute).Name == columnName : ((ForeignKeyAttribute)attribute).Name == columnName) ) ), new DefaultTypeMap(typeof(T)) }) { } } 
0
source share

All Articles