使用类属性手动映射列名称

use*_*985 156 dapper

我是Dapper Micro ORM的新手.到目前为止,我能够将它用于简单的ORM相关内容,但我无法使用类属性映射数据库列名.例如:

我有如下数据库表:

Table Name: Person
person_id  int
first_name varchar(50)
last_name  varchar(50)
Run Code Online (Sandbox Code Playgroud)

我有一个名为Person的课程

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

请注意,表中的列名与我尝试映射查询结果的数据的类的属性名不同.

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

上面的代码不起作用,因为列名与对象的(Person)属性不匹配.在这种情况下,我可以在Dapper中手动映射(例如person_id => PersonId)具有对象属性的列名吗?

任何线索或帮助将受到高度赞赏.

Kal*_*son 182

Dapper现在支持自定义列到属性映射器.它通过ITypeMap接口完成.Dapper提供了一个CustomPropertyTypeMap类,可以完成大部分工作.例如:

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))));
Run Code Online (Sandbox Code Playgroud)

而型号:

public class TModel {
    [Column(Name="my_property")]
    public int MyProperty { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

值得注意的是,CustomPropertyTypeMap的实现要求该属性存在并匹配其中一个列名,否则将不会映射该属性.DefaultTypeMap类提供的标准功能,并且可以被利用来改变这种行为:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

有了它,就可以很容易地创建一个自定义类型映射器,如果它们存在,它们将自动使用属性,否则将回退到标准行为:

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))
            })
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

这意味着我们现在可以轻松支持需要使用属性的地图的类型:

Dapper.SqlMapper.SetTypeMap(
    typeof(MyModel),
    new ColumnAttributeTypeMapper<MyModel>());
Run Code Online (Sandbox Code Playgroud)

这是完整源代码要点.

  • 建议将此作为官方答案 - Dapper的这个功能非常有用. (6认同)
  • 这不能再悲伤了 (4认同)
  • @Oliver发布的映射解决方案(/sf/answers/2439931091/)可以使用,并且需要的代码更少. (3认同)
  • 我喜欢如何轻松地将“轻松地”一词扔掉:P (2认同)

Mar*_*ell 66

有一段时间,以下应该有效:

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
Run Code Online (Sandbox Code Playgroud)

  • 虽然这不是"*手动*使用类属性映射列名称"这一问题的答案,但对我而言,这比必须手动映射要好得多(不幸的是在PostgreSQL中,最好在列名中使用下划线).请不要删除下一版本中的MatchNamesWithUnderscores选项!谢谢!!! (5认同)
  • @victorvartan没有计划删除`MatchNamesWithUnderscores`选项.**充其量**,如果我们重构配置API,我会留下`MatchNamesWithUnderscores`成员(理想情况下仍然可以工作)并添加一个`[Obsolete]`标记来指向人们新的API. (5认同)
  • @MarcGravell在你的回答开头的"一段时间"这句话让我担心你可能会在将来的版本中删除它,谢谢你的澄清!非常感谢Dapper,一个很棒的微型ORM,我刚开始用于一个小项目以及ASP.NET Core上的Npgsql! (4认同)
  • 这无疑是最好的答案。我找到了成堆的解决方法,但最终偶然发现了这一点。很容易成为最好但广告最少的答案。 (2认同)
  • 如果这些年来我必须一直付钱给你,你的回答对我有帮助,我就会成为一个穷人。 (2认同)

Sam*_*ron 63

这很好用:

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;
}
Run Code Online (Sandbox Code Playgroud)

Dapper没有允许你指定列属性的工具,我不反对添加对它的支持,只要我们不引入依赖.

  • Column Attribue可以方便地映射存储过程结果. (57认同)
  • 列属性对于更轻松地促进域与您用于实现实体的工具实现细节之间的紧密物理和/或语义耦合也很有用.因此,不要添加对此的支持!!!! :) (2认同)
  • 我不明白为什么 tableattribute 时没有 columnattribue。此示例如何与插入、更新和 SP 一起使用?我希望看到columnattribe,它非常简单,并且可以使从其他实现类似功能(例如现已不复存在的linq-sql)的解决方案迁移变得非常容易。 (2认同)

Ran*_*ton 28

这是一个简单的解决方案,不需要允许您将基础设施代码保留在POCO之外的属性.

这是一个处理映射的类.如果映射了所有列,字典将起作用,但此类允许您仅指定差异.此外,它还包括反向映射,因此您可以从列中获取字段,从字段中获取列,这在执行生成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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

设置ColumnMap对象并告诉Dapper使用映射.

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])));
Run Code Online (Sandbox Code Playgroud)

  • 由于某些原因,这不再起作用了? (3认同)

lio*_*far 25

我使用动态和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;
    }
Run Code Online (Sandbox Code Playgroud)


Oli*_*ver 12

取自目前在Dapper 1.42上的Dapper Tests.

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

Helper类从Description属性中获取名称(我个人使用过类似@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;
}
Run Code Online (Sandbox Code Playgroud)

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

   [Description("A")]
   public string B { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

  • 为了使其即使对于没有定义描述的属性也能工作,我将 `GetDescriptionFromAttribute` 的返回值更改为 `return (attrib?.Description ?? member.Name).ToLower();` 并添加了 `.ToLower() ` 到 `columnName` 在地图中不应该区分大小写。 (4认同)
  • 谢谢。有没有办法设置每个 SQL 调用的映射而不是全局的?我只需要它来使用我一半的通话。 (2认同)

Bra*_*ess 11

实现此目的的一种简单方法是在查询中的列上使用别名.如果您的数据库列是,PERSON_ID并且您的对象的属性是ID您可以select PERSON_ID as Id ...在您的查询中执行,Dapper将按预期选择它.


mxm*_*ile 10

与映射混淆是边缘进入真正的ORM土地.而不是与它斗争并使Dapper保持其真正的简单(快速)形式,只需稍微修改您的SQL:

var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";
Run Code Online (Sandbox Code Playgroud)


Tad*_*dej 8

在打开与数据库的连接之前,请为每个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))));
Run Code Online (Sandbox Code Playgroud)

然后将数据注释添加到您的poco类,如下所示:

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; }
}
Run Code Online (Sandbox Code Playgroud)

在那之后,你们都准备好了.只需进行查询调用,例如:

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();
}
Run Code Online (Sandbox Code Playgroud)

  • 它需要所有属性都具有 Column 属性。如果映射器不可用,有没有办法用属性映射? (3认同)

chr*_*989 6

这是对其他答案的回避。这只是我管理查询字符串的一个想法。

人物.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";
    }
}
Run Code Online (Sandbox Code Playgroud)

接口方法

using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(Person.Select()).ToList();
    return person;
}
Run Code Online (Sandbox Code Playgroud)


mam*_*ack 5

如果您使用 .NET 4.5.1 或更高版本,请检查Dapper.FluentColumnMapping以映射 LINQ 样式。它使您可以将数据库映射与模型完全分离(无需注释)

  • 我是 Dapper.FluentColumnMapping 的作者。将映射与模型分离是主要设计目标之一。我想将核心数据访问(即存储库接口、模型对象等)与特定于数据库的具体实现隔离,以实现关注点的清晰分离。感谢您的提及,我很高兴您发现它很有用!:-) (6认同)