194 votes

Mappez manuellement les noms de colonnes avec les propriétés de classe

Je suis nouveau dans le micro ORM Dapper. Jusqu'à présent, je suis capable de l'utiliser pour des tâches simples liées à l'ORM mais je ne suis pas capable de mapper les noms des colonnes de la base de données avec les propriétés de la classe.

Par exemple, j'ai la table de base de données suivante :

Nom de la table : Personne
person_id  int
first_name varchar(50)
last_name  varchar(50)

et j'ai une classe appelée Personne :

public class Personne 
{
    public int PersonneId { get; set; }
    public string Prenom { get; set; }
    public string Nom { get; set; }
}

Veuillez noter que les noms de mes colonnes dans la table sont différents du nom de propriété de la classe à laquelle j'essaie de mapper les données que j'ai obtenues du résultat de la requête.

var sql = @"select top 1 PersonneId,Prenom,Nom from Personne";
using (var conn = ConnectionFactory.GetConnection())
{
    var personne = conn.Query(sql).ToList();
    return personne;
}

Le code ci-dessus ne fonctionnera pas car les noms des colonnes ne correspondent pas aux propriétés de l'objet (Personne). Dans ce scénario, y a-t-il quelque chose que je peux faire dans Dapper pour mapper manuellement (par exemple person_id => PersonneId) les noms des colonnes avec les propriétés de l'objet ?

0 votes

214voto

Kaleb Pederson Points 22428

Dapper prend désormais en charge des mappages personnalisés de colonnes vers des propriétés. Il le fait à travers l'interface ITypeMap. Une classe CustomPropertyTypeMap est fournie par Dapper, qui peut effectuer la plupart de ce travail. Par exemple:

Dapper.SqlMapper.SetTypeMap(
    typeof(TModel),
    new CustomPropertyTypeMap(
        typeof(TModel),
        (type, columnName) =>
            type.GetProperties().FirstOrDefault(prop =>
                prop.GetCustomAttributes(false)
                    .OfType()
                    .Any(attr => attr.Name == columnName))));

Et le modèle:

public class TModel {
    [Column(Name="my_property")]
    public int MyProperty { get; set; }
}

Il est important de noter que l'implémentation de CustomPropertyTypeMap exige que l'attribut existe et corresponde à l'un des noms de colonne, sinon la propriété ne sera pas mappée. La classe DefaultTypeMap fournit la fonctionnalité standard et peut être exploitée pour modifier ce comportement:

public class FallbackTypeMapper : SqlMapper.ITypeMap
{
    private readonly IEnumerable _mappers;

    public FallbackTypeMapper(IEnumerable mappers)
    {
        _mappers = mappers;
    }

    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)
            {
            // la CustomPropertyTypeMap ne prend en charge qu'un constructeur sans arguments
            // et lance une exception non implémentée. Pour contourner cela, attrapez et ignorez.
            }
        }
        return null;
    }
    // implémenter d'autres méthodes d'interface de manière similaire

    // requis parfois après la version 1.13 de dapper
    public ConstructorInfo FindExplicitConstructor()
    {
        return _mappers
            .Select(mapper => mapper.FindExplicitConstructor())
            .FirstOrDefault(result => result != null);
    }
}

Et avec cela en place, il devient facile de créer un mappage de type personnalisé qui utilisera automatiquement les attributs s'ils sont présents mais sinon, retournera au comportement standard:

public class ColumnAttributeTypeMapper : FallbackTypeMapper
{
    public ColumnAttributeTypeMapper()
        : base(new SqlMapper.ITypeMap[]
            {
                new CustomPropertyTypeMap(
                   typeof(T),
                   (type, columnName) =>
                       type.GetProperties().FirstOrDefault(prop =>
                           prop.GetCustomAttributes(false)
                               .OfType()
                               .Any(attr => attr.Name == columnName)
                           )
                   ),
                new DefaultTypeMap(typeof(T))
            })
    {
    }
}

Cela signifie que nous pouvons désormais facilement prendre en charge les types qui nécessitent un mappage à l'aide d'attributs:

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

Voici un Gist du code source complet.

0 votes

J'ai eu du mal avec ce même problème ... et il me semble que c'est la voie que je devrais suivre ... Je suis assez confus quant à l'endroit où ce code serait appelé "Dapper.SqlMapper.SetTypeMap(typeof(MyModel), new ColumnAttributeTypeMapper());" stackoverflow.com/questions/14814972/…

0 votes

Vous voudrez l'appeler une fois avant de faire des requêtes. Vous pourriez le faire dans un constructeur statique, par exemple, car il n'a besoin d'être appelé qu'une seule fois.

8 votes

Recommandez de faire de cette réponse la réponse officielle - cette fonctionnalité de Dapper est extrêmement utile.

124voto

Marc Gravell Points 482669

Pendant un certain temps, ce qui suit devrait fonctionner :

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

100voto

Sam Saffron Points 56236

Cela fonctionne bien:

var sql = @"select top 1 person_id PersonId, first_name FirstName, last_name LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query(sql).ToList();
    return person;
}

Dapper n'a pas de fonctionnalité qui vous permet de spécifier un Attribut de Colonne, je ne suis pas contre le fait d'ajouter un support pour cela, à condition que nous ne tirions pas de la dépendance.

0 votes

@Sam Saffron est-il possible pour moi de spécifier l'alias de la table. J'ai une classe nommée Country mais dans la base de données, la table a un nom très compliqué en raison de conventions de nommage archaïques.

75 votes

La colonne Attribute serait pratique pour mapper les résultats de la procédure stockée.

2 votes

Les attributs de colonne seraient également utiles pour faciliter plus facilement le couplage physique et/ou sémantique entre votre domaine et les détails de mise en œuvre de l'outil que vous utilisez pour matérialiser vos entités. Par conséquent, ne rajoutez pas de support pour cela!!!! :)

68voto

liorafar Points 627

Je fais ce qui suit en utilisant dynamic et LINQ:

    var sql = @"select top 1 person_id, first_name, last_name from Person";
    using (var conn = ConnectionFactory.GetConnection())
    {
        List person = conn.Query(sql)
                                  .Select(item => new Person()
                                  {
                                      PersonId = item.person_id,
                                      FirstName = item.first_name,
                                      LastName = item.last_name
                                  }
                                  .ToList();

        return person;
    }

32voto

Randall Sutton Points 514

Voici une solution simple qui ne nécessite pas d'attributs vous permettant de garder le code d'infrastructure hors de vos POCOs.

Il s'agit d'une classe pour gérer les mappings. Un dictionnaire fonctionnerait si vous aviez mappé toutes les colonnes, mais cette classe vous permet de spécifier simplement les différences. De plus, elle inclut des mappings inverses afin que vous puissiez obtenir le champ à partir de la colonne et la colonne à partir du champ, ce qui peut être utile lorsque vous faites des choses telles que la génération de déclarations SQL.

public class ColumnMap
{
    private readonly Dictionary forward = new Dictionary();
    private readonly Dictionary reverse = new Dictionary();

    public void Add(string t1, string t2)
    {
        forward.Add(t1, t2);
        reverse.Add(t2, t1);
    }

    public string this[string index]
    {
       obtenir
        {
            // Vérifiez s'il existe une configuration de colonne personnalisée.
            si (forward.ContainsKey(index))
                return forward[index];
            if (reverse.ContainsKey(index))
                return reverse[index];

            // Si aucun mapping personnalisé n'existe, retournez la valeur passée en argument.
            return index;
        }
    }
}

Configurez l'objet ColumnMap et indiquez à Dapper d'utiliser le mapping.

var columnMap = new ColumnMap();
columnMap.Add("Field1", "Column1");
columnMap.Add("Field2", "Column2");
columnMap.Add("Field3", "Column3");

SqlMapper.SetTypeMap(typeof (MyClass), new CustomPropertyTypeMap(typeof (MyClass), (type, columnName) => type.GetProperty(columnMap[columnName])));

1 votes

Ceci est une bonne solution lorsque vous avez essentiellement un déséquilibre de propriétés dans votre POCO par rapport à ce que votre base de données renvoie, par exemple, une procédure stockée.

1 votes

J'aime un peu la concision que l'utilisation d'un attribut donne, mais conceptuellement, cette méthode est plus propre - elle ne couple pas votre POCO aux détails de la base de données.

1 votes

Si je comprends correctement Dapper, il n'a pas de méthode spécifique Insert(), seulement un Execute()... est-ce que cette approche de mapping fonctionnerait pour les insertions ? Ou les mises à jour ? Merci

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X