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

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에서 열 이름을 개체 속성 으로 수동으로 매핑하기 위해 수행 할 수있는 작업이 있습니까?



답변

이것은 잘 작동합니다 :

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


답변

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.DefaultTypeMap.MatchNamesWithUnderscores = true;


답변

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


답변

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


답변

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


답변

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