Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save senjacob/8539127 to your computer and use it in GitHub Desktop.
Save senjacob/8539127 to your computer and use it in GitHub Desktop.
Map column names with class properties in Dapper-dot-net using ITypeMap and CustomAttributes
namespace YourNamespace
{
/// <summary>
/// Uses the Name value of the ColumnAttribute specified, otherwise maps as usual.
/// </summary>
/// <typeparam name="T">The type of the object that this mapper applies to.</typeparam>
public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
{
public static readonly string ColumnAttributeName = "ColumnAttribute";
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(typeof (T), SelectProperty),
new DefaultTypeMap(typeof (T))
})
{
}
private static PropertyInfo SelectProperty(Type type, string columnName)
{
return
type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).
FirstOrDefault(
prop =>
prop.GetCustomAttributes(false)
// Search properties to find the one ColumnAttribute applied with Name property set as columnName to be Mapped
.Any(attr => attr.GetType().Name == ColumnAttributeName
&&
attr.GetType().GetProperties(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Any(
f =>
f.Name == "Name" &&
f.GetValue(attr).ToString().ToLower() == columnName.ToLower()))
&& // Also ensure the property is not read-only
(prop.DeclaringType == type
? prop.GetSetMethod(true)
: prop.DeclaringType.GetProperty(prop.Name,
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance).GetSetMethod(true)) != null
);
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
public ColumnAttribute() {}
public ColumnAttribute(string Name) { this.Name = Name; }
}
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
{
ConstructorInfo result = mapper.FindConstructor(names, types);
if (result != null)
{
return result;
}
}
catch (NotImplementedException)
{
}
}
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)
{
}
}
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)
{
}
}
return null;
}
}
}
@senjacob
Copy link
Author

Add database column name as ColumnAttribute's Name property and add the attribute to properties to be mapped to.

public class MyModel
{
    [Column("MyDBColumn1")]
    public string MyProperty1 { get; set; }

    [Column(Name="MyDBColumn2")]
    public string MyProperty2 { get; set; }

    public string MyProperty2 { get; set; } // Uses Default Mapping
    // ...
}

Then set the custom TypeMapper for your Model as:

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

For class libraries of .NET Framework >= v4.0, you can use PreApplicationStartMethod to register your classes for custom type mapping.

using System.Web;
using Dapper;

[assembly: PreApplicationStartMethod(typeof(YourNamespace.Initiator), "RegisterModels")]

namespace YourNamespace
{
    public class Initiator
    {
        private static void RegisterModels()
        {
             SqlMapper.SetTypeMap(typeof(MyModel1), new ColumnAttributeTypeMapper<MyModel1>());
             SqlMapper.SetTypeMap(typeof(MyModel2), new ColumnAttributeTypeMapper<MyModel2>());
             // ...
        }
    }
}

Or you can find the classes to which ColumnAttribute is applied through reflection and set type mappings.

        public static void RegisterTypeMaps()
        {
            var mappedTypes = Assembly.GetAssembly(typeof (Initiator)).GetTypes().Where(
                f =>
                f.GetProperties().Any(
                    p =>
                    p.GetCustomAttributes(false).Any(
                        a => a.GetType().Name == ColumnAttributeTypeMapper<dynamic>.ColumnAttributeName)));

            var mapper = typeof(ColumnAttributeTypeMapper<>);
            foreach (var mappedType in mappedTypes)
            {
                var genericType = mapper.MakeGenericType(new[] { mappedType });
                SqlMapper.SetTypeMap(mappedType, Activator.CreateInstance(genericType) as SqlMapper.ITypeMap);
            }
        }

@vynogradskyi
Copy link

Hello, why don't write SelectProperties like this:

    public static PropertyInfo SelectProperties(Type type, string colName)
    {
        return
            type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .FirstOrDefault(
                    p =>
                    p.GetCustomAttributes(typeof(DbColumnAttribute), false)
                        .Any(attr => ((DbColumnAttribute)attr).ColumnName.ToLower() == colName.ToLower()));
    }

@Humberfrench
Copy link

Important.
Class FallbackTypeMapper will have an error => FindExplicitConstructor does not exist in this class.
You must implement this.

Follow the code

    public ConstructorInfo FindExplicitConstructor()
    {
        foreach (var mapper in mappers)
        {
            try
            {
                ConstructorInfo result = mapper.FindExplicitConstructor();
                if (result != null)
                {
                    return result;
                }
            }
            catch (NotImplementedException)
            {
            }
        }
        return null;
    }

@cristian-lopes
Copy link

Perfect!!

@azilberbaum
Copy link

Anyone want to tackle saving via Dapper.Contrib?

That would be awesome if the mapping worked there as well via Objects!

@oghenez
Copy link

oghenez commented Mar 20, 2019

pls what is the minimum version of dapper supported?

@rrbb7799
Copy link

Thanks for the mapper class!
Project builds but fails on runtime:
System.Data.SqlClient.SqlException : Invalid column name 'xyz...'

@senjacob
Copy link
Author

senjacob commented Feb 4, 2020

Please check the TestCustomTypeMap test samples for preferred way of doing custom typemapping with a DescriptionAttribute..

@emaborsa
Copy link

Anyone want to tackle saving via Dapper.Contrib?

That would be awesome if the mapping worked there as well via Objects!

Have you been able to?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment