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


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.