Dapper con asignación de atributos

c# dapper system.reflection

Pregunta

Intento mapear mis campos Id con los Atributos de columna, pero por alguna razón esto parece no funcionar y no puedo entender por qué. Configuré un proyecto de prueba para demostrar lo que estoy intentando.

Primero, obtuve mis 2 entidades:

Entidad 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();
        }
    }
}

y entidad 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; }
    }
}

En mi base de datos obtuve 2 tablas, también llamadas Table1 y Table2. Ambas tablas tienen sus columnas nombradas iguales a las entidades con la excepción de que Table1 tiene una columna llamada Table2Id y también hay una clave externa entre Table1.Table2Id y Table2.Id.

También hay 1 registro cada uno en ambas tablas y aquellos obtuvieron ambos el Id 2.

Lo que intento a continuación es ejecutar una consulta con dapper y devolver un objeto de tipo Table1. Esto funciona, pero tanto la propiedad Table1.Id como Table1.Table2.Id permanece en 0 (número entero predeterminado). Espero que los atributos de la columna mapearán los campos Id pero claramente esto no está ocurriendo.

Esta es la consulta y la asignación que estoy ejecutando en el código:

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;
}

Ahora podría cambiar el nombre de los dos campos de propiedad Id en las entidades a Table1Id y Table2Id, pero prefiero Id en lugar de la causa del código más lógico, como Table1.Id en lugar de Table1.Table1Id. Entonces me preguntaba si es posible lo que quiero aquí y, de ser así, ¿cómo?

Editar:

Encontré este tema: Asignar nombres de columna manualmente con propiedades de clase

Y con el código en la primera publicación de Kaleb Pederson, es posible usar atributos cuando sea necesario con la clase FallBackTypeMapper y la clase ColumnAttributeTypeMapper. Todo lo que se necesita es agregar las clases requeridas al mapeo de tipos con:

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

Pero con muchas entidades, esta lista crecerá largamente. También necesita agregar cada clase manualmente a la lista y me preguntaba si esto podría hacerse de forma automática en más genérico con Reflection. Encontré un fragmento de código que puede obtener todos los tipos:

        const string @namespace = "DapperTestProj.Entities";

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

Y recorriendo todos los tipos, puedo hacer esto, el único problema que tengo ahora es ¿qué fragmento de código necesito tener o necesito poner en el lugar donde están ahora los interrogantes?

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

Editar:

Después de más búsquedas, encontré la solución para mi último problema:

        typeList.ToList().ForEach(type =>
            {
                var mapper = (SqlMapper.ITypeMap)Activator.CreateInstance(
                    typeof(ColumnAttributeTypeMapper<>)
                        .MakeGenericType(type));
                SqlMapper.SetTypeMap(type, mapper);
            });

Respuesta aceptada

Para completar la solución, quiero compartir el código que encontré y reunir con aquellos que estén interesados.

En lugar de (ab) usar System.Data.Linq.Mapping.ColumnAttribute, podría ser más lógica (y probablemente guardar, aunque la posibilidad será muy pequeña de que Microsoft cambie la clase linq a sql ColumnAttribute) para crear nuestro propio ColumnAttribute clase:

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;
        }
    }
}

Se encuentra en el tema que mencioné anteriormente, las clases FallBackTypeMapper y ColumnAttributeTypeMapper:

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)) 
                    })
        {
        }
    }
}

y finalmente, TypeMapper.cs para inicializar la asignación.

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 type in Assembly.GetExecutingAssembly().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);
            });
        }
    }
}

Al inicio, TypeMapper.Initialize necesita ser llamado:

TypeMapper.Initialize("DapperTestProj.Entities");

Y puede comenzar a usar atributos para las propiedades de la entidad

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();
        }
    }
}

Respuesta popular

La respuesta de Cornelis es correcta, sin embargo, quería agregar una actualización a esto. A partir de la versión actual de Dapper, también necesita implementar SqlMapper.ItypeMap.FindExplicitConstructor() . No estoy seguro de cuándo se realizó este cambio, pero esto para cualquier persona que tropieza con esta pregunta y le falta esa parte de la solución.

Dentro de FallbackTypeMapper.cs

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

También puede usar la clase ColumnAttribute ubicada dentro del espacio de nombres System.ComponentModel.DataAnnotations.Schema lugar de renovar su propia versión ColumnAttribute no sea de base de datos / orm.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué