web-dev-qa-db-fra.com

Mapper manuellement les noms de colonne avec les propriétés de classe

Je suis nouveau sur Dapper Micro ORM. Jusqu'à présent, je suis capable de l'utiliser pour des choses simples liées à l'ORM, mais je ne suis pas en mesure 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 comme suit:

Table Name: Person
person_id  int
first_name varchar(50)
last_name  varchar(50)

et j'ai la classe appelée personne

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Veuillez noter que mes noms de colonne 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 à partir du résultat de la requête.

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

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

Tout indice ou aide serait très apprécié.

136
user1154985

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<Person>(sql).ToList();
    return person;
}

Dapper ne dispose d'aucune fonctionnalité vous permettant de spécifier un Attribut de colonne , je ne suis pas contre l'ajout d'un support, à condition de ne pas extraire la dépendance. 

60
Sam Saffron

Dapper prend désormais en charge les mappeurs de colonne personnalisés pour les propriétés. Cela se fait via l'interface ITypeMap . Un CustomPropertyTypeMap class est fourni par Dapper qui peut faire 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<ColumnAttribute>()
                    .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 la mise en œuvre de CustomPropertyTypeMap nécessite que l'attribut existe et corresponde à l'un des noms de colonne ou la propriété ne sera pas mappée. Le DefaultTypeMap class fournit la fonctionnalité standard et peut être exploité pour modifier ce comportement:

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

    public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> 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)
            {
            // the CustomPropertyTypeMap only supports a no-args
            // constructor and throws a not implemented exception.
            // to work around that, catch and ignore.
            }
        }
        return null;
    }
    // implement other interface methods similarly

    // required sometime after version 1.13 of 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 mappeur de type personnalisé qui utilisera automatiquement les attributs s'ils sont présents, mais retombera dans le comportement standard:

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(attr => attr.Name == columnName)
                           )
                   ),
                new DefaultTypeMap(typeof(T))
            })
    {
    }
}

Cela signifie que nous pouvons maintenant facilement prendre en charge les types nécessitant une carte utilisant des attributs:

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

Voici un Gist au code source complet .

164
Kaleb Pederson

Pendant un certain temps, ce qui suit devrait fonctionner:

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
55
Marc Gravell

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

Ceci est une classe pour traiter les mappages. Un dictionnaire fonctionnerait si vous mappiez toutes les colonnes, mais cette classe vous permet de spécifier uniquement les différences. De plus, il inclut des cartes inversées pour vous permettre d’obtenir le champ de la colonne et la colonne du champ, ce qui peut être utile lors de tâches telles que la génération d’instructions SQL. 

public class ColumnMap
{
    private readonly Dictionary<string, string> forward = new Dictionary<string, string>();
    private readonly Dictionary<string, string> reverse = new Dictionary<string, string>();

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

    public string this[string index]
    {
        get
        {
            // Check for a custom column map.
            if (forward.ContainsKey(index))
                return forward[index];
            if (reverse.ContainsKey(index))
                return reverse[index];

            // If no custom mapping exists, return the value passed in.
            return index;
        }
    }
}

Configurez l'objet ColumnMap et dites à Dapper d'utiliser le mappage.

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])));
26
Randall Sutton

Je fais ce qui suit en utilisant dynamique et LINQ: 

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

        return person;
    }
16
liorafar

Un moyen simple d'y parvenir consiste simplement à utiliser des alias sur les colonnes de votre requête. Si votre colonne de base de données est PERSON_ID et que la propriété de votre objet est ID, vous pouvez simplement faire select PERSON_ID as Id ... dans votre requête et Dapper le prendra comme prévu.

11
Brad Westness

Tiré de Dapper Tests qui est actuellement sur Dapper 1.42.

// custom mapping
var map = new CustomPropertyTypeMap(typeof(TypeWithMapping), 
                                    (type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
Dapper.SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);

Classe d'assistance pour obtenir le nom de l'attribut Description (j'ai personnellement utilisé une colonne comme exemple @kalebs)

static string GetDescriptionFromAttribute(MemberInfo member)
{
   if (member == null) return null;

   var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
   return attrib == null ? null : attrib.Description;
}

Classe

public class TypeWithMapping
{
   [Description("B")]
   public string A { get; set; }

   [Description("A")]
   public string B { get; set; }
}
11
Oliver

S'amuser avec la cartographie est la limite se déplaçant dans de véritables terres ORM. Au lieu de vous battre et de garder Dapper dans sa forme la plus simple (rapide), modifiez simplement votre code SQL de la manière suivante:

var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";
9
mxmissile

Avant d'ouvrir la connexion à votre base de données, exécutez ce morceau de code pour chacune de vos classes poco:

// Section
SqlMapper.SetTypeMap(typeof(Section), new CustomPropertyTypeMap(
    typeof(Section), (type, columnName) => type.GetProperties().FirstOrDefault(prop =>
    prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));

Ajoutez ensuite les annotations de données à vos classes poco comme ceci:

public class Section
{
    [Column("db_column_name1")] // Side note: if you create aliases, then they would match this.
    public int Id { get; set; }
    [Column("db_column_name2")]
    public string Title { get; set; }
}

Après cela, vous êtes tous ensemble. Il suffit de faire un appel de requête, quelque chose comme:

using (var sqlConnection = new SqlConnection("your_connection_string"))
{
    var sqlStatement = "SELECT " +
                "db_column_name1, " +
                "db_column_name2 " +
                "FROM your_table";

    return sqlConnection.Query<Section>(sqlStatement).AsList();
}
7
JedatKinports

Ceci est en train de reculer d’autres réponses. C'est juste une pensée que j'avais pour gérer les chaînes de requête.

Person.cs

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public static string Select() 
    {
        return $"select top 1 person_id {nameof(PersonId)}, first_name {nameof(FirstName)}, last_name {nameof(LastName)}from Person";
    }
}

Méthode API

using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(Person.Select()).ToList();
    return person;
}
3
christo8989

Si vous utilisez .NET 4.5.1 ou une version plus récente de la caisse Dapper.FluentColumnMapping pour mapper le style LINQ. Il vous permet de séparer complètement le mappage de la base de données de votre modèle (aucune annotation requise)

3
mamuesstack

pour tous ceux qui utilisent Dapper 1.12, voici ce que vous devez faire pour y parvenir:

  • Ajoutez une nouvelle classe d'attribut de colonne:
      [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]
    
      public class ColumnAttribute : Attribute
      {
    
        public string Name { get; set; }
    
        public ColumnAttribute(string name)
        {
          this.Name = name;
        }
      }
    
  • Recherchez cette ligne:
    map = new DefaultTypeMap(type);
    

    et commenter.

  • Écrivez ceci à la place:
            map = new CustomPropertyTypeMap(type, (t, columnName) =>
            {
              PropertyInfo pi = t.GetProperties().FirstOrDefault(prop =>
                                prop.GetCustomAttributes(false)
                                    .OfType<ColumnAttribute>()
                                    .Any(attr => attr.Name == columnName));
    
              return pi != null ? pi : t.GetProperties().FirstOrDefault(prop => prop.Name == columnName);
            });
    
  • 1
    Uri Abramson

    La solution de Kaleb Pederson a fonctionné pour moi. J'ai mis à jour ColumnAttributeTypeMapper pour autoriser un attribut personnalisé (il fallait deux mappages différents sur le même objet de domaine) et mis à jour les propriétés pour autoriser les installateurs privés dans les cas où un champ devait être dérivé et les types différents.

    public class ColumnAttributeTypeMapper<T,A> : FallbackTypeMapper where A : ColumnAttribute
    {
        public ColumnAttributeTypeMapper()
            : base(new SqlMapper.ITypeMap[]
                {
                    new CustomPropertyTypeMap(
                       typeof(T),
                       (type, columnName) =>
                           type.GetProperties( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(prop =>
                               prop.GetCustomAttributes(true)
                                   .OfType<A>()
                                   .Any(attr => attr.Name == columnName)
                               )
                       ),
                    new DefaultTypeMap(typeof(T))
                })
        {
            //
        }
    }
    
    1
    GameSalutes

    La solution simple au problème que Kaleb tente de résoudre consiste simplement à accepter le nom de la propriété si l'attribut de colonne n'existe pas:

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

    Je sais que c'est un fil relativement ancien, mais je pensais jeter ce que j'ai fait là-bas.

    Je voulais que le mappage d'attributs fonctionne globalement. Soit vous correspondez le nom de la propriété (aka default), soit vous correspondez à un attribut de colonne sur la propriété de classe Je ne voulais pas non plus avoir à configurer cela pour chaque classe à laquelle je mappais. En tant que tel, j'ai créé une classe DapperStart que j'appelle au démarrage de l'application:

    public static class DapperStart
    {
        public static void Bootstrap()
        {
            Dapper.SqlMapper.TypeMapProvider = type =>
            {
                return new CustomPropertyTypeMap(typeof(CreateChatRequestResponse),
                    (t, columnName) => t.GetProperties().FirstOrDefault(prop =>
                        {
                            return prop.Name == columnName || prop.GetCustomAttributes(false).OfType<ColumnAttribute>()
                                       .Any(attr => attr.Name == columnName);
                        }
                    ));
            };
        }
    }
    

    Assez simple. Je ne suis pas sûr des problèmes que je rencontrerai pour l'instant car je viens d'écrire ceci, mais cela fonctionne.

    0
    Matt M