클래스 속성으로 열 이름을 수동으로 매핑


173

Dapper micro ORM을 처음 사용합니다. 지금까지 간단한 ORM 관련 항목에 사용할 수는 있지만 데이터베이스 열 이름을 클래스 속성과 매핑 할 수는 없습니다.

예를 들어 다음 데이터베이스 테이블이 있습니다.

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

Person이라는 클래스가 있습니다.

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

테이블의 열 이름이 쿼리 결과에서 얻은 데이터를 매핑하려고하는 클래스의 속성 이름과 다릅니다.

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

열 이름이 개체의 (개인) 속성과 일치하지 않으므로 위의 코드는 작동하지 않습니다. 이 시나리오에서 Dapper person_id => PersonId에서 열 이름을 개체 속성 으로 수동으로 매핑하기 위해 수행 할 수있는 작업이 있습니까?


답변:


80

이것은 잘 작동합니다 :

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에는 열 속성 을 지정할 수있는 기능이 없으므로 종속성을 가져 오지 않으면 지원을 추가하는 데 반대하지 않습니다.


@ Sam Saffron은 테이블 별칭을 지정할 수있는 방법이 있습니다. Country라는 클래스가 있지만 db에서 고전 명명 규칙으로 인해 테이블의 이름이 매우 복잡합니다.
TheVillageIdiot

64
Column Attribue은 저장 프로 시저 결과를 매핑하는 데 유용합니다.
Ronnie Overby

2
열 속성은 도메인과 엔티티 구현에 사용하는 도구 구현 세부 정보 사이의 물리적 및 / 또는 의미 론적 결합을보다 쉽게 ​​촉진하는 데 유용합니다. 따라서 이것에 대한 지원을 추가하지 마십시오 !!!! :)
Derek Greer

tableattribute 때 columnattribe가없는 이유를 모르겠습니다. 이 예제는 삽입, 업데이트 및 SP에서 어떻게 작동합니까? columnattribe를보고 싶습니다. 죽은 단순하고 현재 기능이없는 linq-sql과 비슷한 것을 구현하는 다른 솔루션에서 삶을 매우 쉽게 마이그레이션 할 수 있습니다.
Vman

197

Dapper는 이제 사용자 지정 열 대 속성 매퍼를 지원합니다. ITypeMap 인터페이스를 통해 수행 됩니다. 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))));

그리고 모델 :

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

그것은 있음을 유의해야 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);
    }
}

그리고 그 속성을 사용하면 속성이있는 경우 자동으로 사용하지만 표준 동작으로 돌아가는 사용자 정의 유형 매퍼를 쉽게 만들 수 있습니다.

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

즉, 속성을 사용하여 맵이 필요한 유형을 쉽게 지원할 수 있습니다.

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

다음 은 전체 소스 코드에 대한 요지 입니다.


나는이 같은 문제로 어려움을 겪고 있습니다 ... 그리고 이것은 내가 가야 할 경로처럼 보입니다 ...이 코드가 어디에서 "Dapper.SqlMapper.SetTypeMap (typeof (MyModel), 새 ColumnAttributeTypeMapper <MyModel> ()); " stackoverflow.com/questions/14814972/…
Rohan Büchner 2013

쿼리하기 전에 한 번만 호출하면됩니다. 예를 들어 한 번만 호출하면되므로 정적 생성자에서이를 수행 할 수 있습니다.
Kaleb Pederson

7
이것을 공식 답변으로 만드는 것이 좋습니다. Dapper의이 기능은 매우 유용합니다.
killthrush

3
@Oliver ( stackoverflow.com/a/34856158/364568 )가 게시 한 매핑 솔루션이 작동하며 코드가 더 적습니다.
리가

4
나는 "쉽게"라는 단어가 그렇게 쉽게 던져지는 것을 좋아합니다. : P
Jonathan B.

80

얼마 동안 다음이 작동해야합니다.

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

6
이것은 실제로 " 클래스 속성으로 열 이름을 수동으로 매핑 "에 대한 대답은 아니지만 수동 으로 매핑하는 것보다 훨씬 낫습니다 (불행히도 PostgreSQL에서는 열 이름에 밑줄을 사용하는 것이 좋습니다). 다음 버전에서는 MatchNamesWithUnderscores 옵션을 제거하지 마십시오! 감사합니다!!!
victorvartan

5
@victorvartan MatchNamesWithUnderscores옵션 을 제거 할 계획이 없습니다 . 기껏 해야 구성 API를 리팩터링하면 MatchNamesWithUnderscores멤버를 그대로두고 (이상적으로 작동하는 경우) [Obsolete]사람들을 새 API로 안내 하는 마커를 추가합니다 .
Marc Gravell

4
@MarcGravell 답변의 시작 부분에 "잠깐 동안"이라는 단어가 명확하게 해주셔서 감사합니다. ASP.NET Core에서 Npgsql과 함께 작은 프로젝트에 사용하기 시작한 멋진 마이크로 ORM 인 Dapper에 대해 대단히 감사합니다!
victorvartan

2
이것은 가장 좋은 대답입니다. 나는 더미와 주변의 더미를 찾았지만 마침내 이것에 걸려 들었습니다. 가장 우수하지만 광고가 가장 적은 답변입니다.
teaMonkeyFruit

29

다음은 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;
        }
    }
}

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

이는 기본적으로 POCO의 속성이 데이터베이스에서 반환하는 항목 (예 : 저장 프로 시저)과 일치하지 않을 때 좋은 솔루션입니다.
호감

1
나는 속성을 사용하는 것이 간결함과 비슷하지만 개념적 으로이 방법이 더 깨끗합니다 .POCO를 데이터베이스 세부 정보에 연결하지 않습니다.
브루노 브랜트

Dapper를 올바르게 이해하면 특정 Insert () 메서드가 없으며 Execute () 만 있습니다.이 매핑 방식은 삽입에 효과적입니까? 아니면 업데이트? 감사합니다
UuDdLrLrSs

29

동적 및 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;
    }

12

이를 달성하는 쉬운 방법은 쿼리의 열에 별칭을 사용하는 것입니다. 데이터베이스 열이 PERSON_ID있고 객체의 속성이 ID있다면 select PERSON_ID as Id ...쿼리에서 수행 할 수 있으며 Dapper가 예상대로 가져옵니다.


12

현재 Dapper 1.42에 있는 Dapper 테스트 에서 가져 왔습니다 .

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

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

수업

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

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

1
더 설명이 정의되지 않은 곳이 속성에도 적용하기 위해서는, 나는의 반환 변경 GetDescriptionFromAttribute에를 return (attrib?.Description ?? member.Name).ToLower();하고 추가 .ToLower()columnName지도에서 대소 문자를 구분해서는 안됩니다.
Sam White

11

매핑으로 인한 혼란은 실제 ORM 토지로 이동하는 경계선입니다. 그것과 싸우고 Dapper를 단순하고 빠른 형태로 유지하는 대신 SQL을 약간 다음과 같이 수정하십시오.

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

8

데이터베이스에 대한 연결을 열기 전에 각 포코 클래스에 대해 다음 코드를 실행하십시오.

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

그런 다음 포코 클래스에 다음과 같이 데이터 주석을 추가하십시오.

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

그 후에는 모두 준비되었습니다. 다음과 같은 쿼리 호출을 만드십시오.

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

1
모든 속성에는 열 속성이 있어야합니다. 매퍼를 사용할 수없는 경우 속성으로 매핑하는 방법이 있습니까?
sandeep.gosavi

5

LINQ 스타일을 매핑하기 위해 .NET 4.5.1 이상 체크 아웃 Dapper.FluentColumnMapping 을 사용하는 경우 DB 매핑을 모델과 완전히 분리 할 수 ​​있습니다 (주석 필요 없음)


5
저는 Dapper.FluentColumnMapping의 저자입니다. 모델에서 매핑을 분리하는 것이 주요 디자인 목표 중 하나였습니다. 핵심 데이터 액세스 (예 : 리포지토리 인터페이스, 모델 객체 등)를 데이터베이스 별 구체적인 구현에서 격리하여 문제를 완전히 분리하고 싶었습니다. 언급 해 주셔서 감사합니다. 유용하다고 생각합니다. :-)
Alexander

github.com/henkmollema/Dapper-FluentMap 도 비슷합니다. 그러나 더 이상 타사 패키지가 필요하지 않습니다. Dapper는 Dapper.SqlMapper를 추가했습니다. 당신이 관심이 있다면 자세한 내용은 내 답변을 참조하십시오.
Tadej

4

이것은 다른 답변을 피하는 돼지입니다. 쿼리 문자열 관리에 대한 생각 일뿐입니다.

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

API 방법

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

1

Dapper 1.12를 사용하는 모든 사람들을 위해 다음과 같이하십시오.

  • 새 열 속성 클래스를 추가하십시오.

      [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]
    
      public class ColumnAttribute : Attribute
      {
    
        public string Name { get; set; }
    
        public ColumnAttribute(string name)
        {
          this.Name = name;
        }
      }

  • 이 줄을 검색하십시오.

    map = new DefaultTypeMap(type);

    의견을 말하십시오.

  • 대신 이것을 쓰십시오 :

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


  • 잘 모르겠습니다. 사용자가 열을 기준으로 속성 매핑을 수행 할 수 있도록 Dapper를 변경하는 것이 좋습니다. 그렇다면 Dapper를 변경하지 않고 위에 게시 한 코드를 사용하는 것이 가능합니다.
    Kaleb Pederson

    1
    그러나 모델 유형 각각에 대해 매핑 기능을 호출해야합니다. 모든 유형의 각 유형에 대한 매핑을 호출하지 않고도 속성을 사용할 수 있도록 일반적인 솔루션에 관심이 있습니다.
    Uri Abramson

    2
    @UriAbramson이 언급 한 이유로 대체 할 수있는 전략 패턴을 사용하여 DefaultTypeMap을 구현하고 싶습니다. 참조 code.google.com/p/dapper-dot-net/issues/detail?id=140
    리차드 콜리에게

    1

    Kaleb Pederson의 솔루션이 저에게 효과적이었습니다. 필자는 사용자 지정 특성 (동일한 도메인 개체에 대해 서로 다른 두 개의 매핑이 필요함)을 허용하도록 ColumnAttributeTypeMapper를 업데이트하고 필드를 파생해야하고 유형이 다른 경우 개인 설정자를 허용하도록 속성을 업데이트했습니다.

    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

    나는 이것이 비교적 오래된 실이라는 것을 알고 있지만, 내가 한 일을 버릴 것이라고 생각했다.

    속성 매핑이 전 세계적으로 작동하기를 원했습니다. 속성 이름 (일명 기본값)과 일치하거나 클래스 속성의 열 속성과 일치합니다. 또한 매핑하려는 모든 단일 클래스에 대해 이것을 설정하고 싶지 않았습니다. 따라서 앱 시작시 호출하는 DapperStart 클래스를 만들었습니다.

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

    꽤 간단합니다. 방금 썼을 때 어떤 문제가 발생했는지 확실하지 않지만 작동합니다.


    CreateChatRequestResponse는 어떻게 생겼습니까? 또한 스타트 업에서 어떻게 호출합니까?
    Glen F.

    1
    @GlenF. 요점은 CreateChatRequestResponse의 모양이 중요하지 않다는 것입니다. 어떤 POCO라도 될 수 있습니다. 이것은 당신의 시작에서 호출됩니다. 앱 시작시 StartUp.cs 또는 Global.asax에서 호출 할 수 있습니다.
    매트 M

    아마도 나는 완전히 틀렸지 만 이것이 모든 Entity 객체를 반복하는 방법 CreateChatRequestResponse으로 대체 되지 않는 한 T. 내가 틀렸다면 정정 해주세요.
    Fwd079

    0

    Kaleb이 해결하려는 문제에 대한 간단한 해결책은 열 속성이 존재하지 않는 경우 속성 이름을 수락하는 것입니다.

    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)));
    
    당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
    Licensed under cc by-sa 3.0 with attribution required.