DEV Community

Shafqat Ahmed
Shafqat Ahmed

Posted on

Smarter Generic Mapping in Dapper.FluentMap while using DataAnnotations

I am using postgres sql as database. I have mapped my classes earlier using Entity Framework and used the Column[("name")] attribute. Now that I have switched to Dapper, it seemed Dapper does not read any of the column attributes that I have used in my DTO/POCO classes. Ah! That's going to be a problem.

So my class kinda looks like this

public class Role
{
    [Column("role_id")]
    public long RoleID { get; set; }

    [Column("role_name")]
    public string RoleName { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Now this is going to be an issue. I could rewrite my queries like this ---

const string sql =
    @"SELECT role_id      AS RoleID,
             role_name    AS RoleName
        FROM public.role
       WHERE role_id = @id;";
Enter fullscreen mode Exit fullscreen mode

Now this is a hassle that I dont want to deal with. It seems Dapper.FluentMap has a way to map these column names to properties but that does not happen thorough attributes. Here is how to do that ---

public class RoleMap : EntityMap<Role>
{
    public RoleMap()
    {
        Map(r => r.RoleID).ToColumn("role_id");
        Map(r => r.RoleName).ToColumn("role_name");
    }
}
Enter fullscreen mode Exit fullscreen mode

and then register the mapping class like this

FluentMapper.Initialize(c => c.AddMap(new RoleMap()));
Enter fullscreen mode Exit fullscreen mode

Now I ain't going to write one class for each entities that I have created. So I created a generic class called the ColumnMapper and that takes care of the mapping if your DTO/POCO class uses System.ComponentModel.DataAnnotations.Schema and maps using the ColumnAttribute. Which I already do. So here is the class that does that.

public class ColumnMapper < T >: EntityMap < T > where T: class {
    public ColumnMapper() {
      // Get all public instance properties of the entity type T.
      PropertyInfo[] infos = typeof (T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

      foreach(var propertyInfo in infos) {
        // Check if the property has a [Column] attribute.
        var columnAttr = propertyInfo.GetCustomAttribute < System.ComponentModel.DataAnnotations.Schema.ColumnAttribute > ();

        if (columnAttr != null) {
          // --- Reflection to call Map(lambda).ToColumn(columnName) ---


          var parameter = Expression.Parameter(typeof (T), "x");
          var property = Expression.Property(parameter, propertyInfo);

          var convert = Expression.Convert(property, typeof (object));
          var lambda = Expression.Lambda < Func < T,
            object >> (convert, parameter);

           var mapMethod = typeof (EntityMap < T > )
            .GetMethod("Map", BindingFlags.Instance | BindingFlags.NonPublic);

          if (mapMethod == null) {

            throw new InvalidOperationException("Could not find the 'Map' method via reflection. The Dapper.FluentMap API may have changed.");
          }

          var propertyMap = mapMethod.Invoke(this, new object[] {
            lambda
          });

          var toColumnMethod = propertyMap.GetType()
            .GetMethod("ToColumn", new [] {
              typeof (string), typeof (bool)
            });

          if (toColumnMethod != null) {
            toColumnMethod.Invoke(propertyMap, new object[] {
              columnAttr.Name, false
            });
          }
        }
      }
    }
Enter fullscreen mode Exit fullscreen mode

Then I can call this generic mapper class like this

 FluentMapper.Initialize(cfg =>
        {
            cfg.AddMap(new ColumnMapper<User>());
            cfg.AddMap(new ColumnMapper<Role>());
            cfg.AddMap(new ColumnMapper<Permission>());
            cfg.AddMap(new ColumnMapper<RolePermissionMap>());
            cfg.AddMap(new ColumnMapper<RoleUserMap>());
            cfg.AddMap(new ColumnMapper<ServiceInfo>());
            cfg.AddMap(new ColumnMapper<ServiceInstance>());
            cfg.AddMap(new ColumnMapper<Queue>());
            cfg.AddMap(new ColumnMapper<Token>());
        });
Enter fullscreen mode Exit fullscreen mode

Walla! I am done!

Since this is called only once during startup it has no performance issues either. Let me know what you guys think.

Top comments (0)