Dapper avec mappage d'attributs

c# dapper system.reflection

Question

J'essaie de mapper mes champs Id avec les attributs de colonne, mais pour une raison quelconque, cela ne semble pas fonctionner et je n'arrive pas à comprendre pourquoi. J'ai mis en place un projet de test pour démontrer ce que j'essaie.

Tout d'abord, j'ai mes 2 entités:

Table d'entité1

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

et entité 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; }
    }
}

Dans ma base de données, j'ai reçu 2 tables, également nommées Table1 et Table2. Les deux tables ont leurs colonnes nommées égales aux entités à l'exception que Table1 a une colonne nommée Table2Id et qu'il existe également une clé étrangère entre Table1.Table2Id et Table2.Id.

Il y a aussi 1 enregistrement chacun dans les deux tableaux et ceux-ci ont tous deux l'id 2.

Ce que j'essaie ensuite, c'est d'exécuter une requête avec dapper et elle doit retourner un objet de type Table1. Cela fonctionne, mais à la fois la propriété Table1.Id et Table1.Table2.Id reste 0 (entier par défaut). Je m'attends à ce que les attributs de la colonne mappent les champs Id mais clairement, cela ne va pas.

C'est la requête et le mappage que j'exécute dans le 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;
}

Maintenant, je pourrais renommer les deux champs de propriété Id dans les entités à Table1Id et Table2Id mais je préfère Id à la place du code plus logique comme Table1.Id au lieu de Table1.Table1Id. Alors je me demandais, est-ce possible ce que je veux ici et si oui, comment?

Modifier:

J'ai trouvé ce sujet: Mapper manuellement les noms de colonne avec les propriétés de classe

Et avec le code dans le premier message de Kaleb Pederson, il est possible d'utiliser des attributs lorsque cela est nécessaire avec la classe FallBackTypeMapper et la classe ColumnAttributeTypeMapper. Il suffit d'ajouter les classes requises au mappage de caractères avec:

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

Mais avec de nombreuses entités, cette liste va s'allonger. Aussi, vous devez ajouter chaque classe manuellement à la liste et je me demandais si cela pouvait être fait automatiquement et plus générique avec Reflection. J'ai trouvé un fragment de code capable d'obtenir tous les types:

        const string @namespace = "DapperTestProj.Entities";

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

Et en parcourant tous les types, je peux le faire, le seul problème que j'ai maintenant est de savoir quel fragment de code dois-je ou dois-je mettre à l'endroit où les repères sont en ce moment?

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

Modifier:

Après plus de recherches, j'ai trouvé la solution à mon dernier problème:

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

Réponse acceptée

Pour compléter la solution, je souhaite partager le code que j'ai trouvé et rassemblé avec ceux qui sont intéressés.

Au lieu de (ab) en utilisant System.Data.Linq.Mapping.ColumnAttribute, cela pourrait être plus logique (et probablement enregistrer, bien que la probabilité que Microsoft modifie la classe linq en sql ColumnAttribute soit faible) pour créer notre propre ColumnAttribute classe:

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

Trouvé dans la rubrique que j'ai mentionnée précédemment, les classes FallBackTypeMapper et 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)) 
                    })
        {
        }
    }
}

et enfin, le TypeMapper.cs pour initialiser le mappage.

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

Au démarrage, TypeMapper.Initialize doit être appelé:

TypeMapper.Initialize("DapperTestProj.Entities");

Et vous pouvez commencer à utiliser des attributs pour les propriétés de l'entité

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

Réponse populaire

La réponse de Cornelis est correcte, cependant je voulais ajouter une mise à jour à cela. A partir de la version actuelle de Dapper, vous devez également implémenter SqlMapper.ItypeMap.FindExplicitConstructor() . Je ne suis pas sûr quand ce changement a été fait, mais ceci pour quiconque trébuche sur cette question et manque cette partie de la solution.

Dans FallbackTypeMapper.cs

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

Vous pouvez également utiliser la classe ColumnAttribute située dans l'espace de noms System.ComponentModel.DataAnnotations.Schema au lieu de déployer la vôtre pour une version intégrée non spécifique à la base de données / orm.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi