[dapper] Dapper에서 멀티 매핑의 올바른 사용

dapper의 Multimapping 기능을 사용하여 ProductItem 및 관련 고객 목록을 반환하려고합니다.

[Table("Product")]
public class ProductItem
{
    public decimal ProductID { get; set; }
    public string ProductName { get; set; }
    public string AccountOpened { get; set; }
    public Customer Customer { get; set; }
}

public class Customer
{
    public decimal CustomerId { get; set; }
    public string CustomerName { get; set; }
}

내 멋진 코드는 다음과 같습니다.

var sql = @"select * from Product p
            inner join Customer c on p.CustomerId = c.CustomerId
            order by p.ProductName";

var data = con.Query<ProductItem, Customer, ProductItem>(
    sql,
    (productItem, customer) => {
        productItem.Customer = customer;
        return productItem;
    },
    splitOn: "CustomerId,CustomerName"
);

이것은 잘 작동하지만 모든 고객 속성을 반환하려면 splitOn 매개 변수에 전체 열 목록을 추가해야하는 것 같습니다. “CustomerName”을 추가하지 않으면 null이 반환됩니다. 멀티 매핑 기능의 핵심 기능을 잘못 이해하고 있습니까? 매번 전체 열 이름 목록을 추가하고 싶지 않습니다.



답변

방금 잘 작동하는 테스트를 실행했습니다.

var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(1 as decimal) CustomerId, 'name' CustomerName";

var item = connection.Query<ProductItem, Customer, ProductItem>(sql,
    (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();

item.Customer.CustomerId.IsEqualTo(1);

splitOn 매개 변수는 분할 지점으로 지정해야하며 기본값은 Id입니다. 분할 지점이 여러 개인 경우 쉼표로 구분 된 목록에 추가해야합니다.

레코드 세트가 다음과 같다고 가정합니다.

ProductID | ProductName | AccountOpened | CustomerId | 고객 이름
--------------------------------------- ----------- --------------

Dapper는이 순서로 열을 2 개의 개체로 분할하는 방법을 알아야합니다. 대충 살펴 쇼는 고객이 열에서 시작하는 것이 CustomerId따라서, splitOn: CustomerId.

기본 테이블의 열 순서가 어떤 이유로 이성을 상실하는 경우주의해야 할 점은, 여기 :

ProductID | ProductName | AccountOpened | CustomerName | 고객 ID
--------------------------------------- ----------- --------------

splitOn: CustomerId 고객 이름이 null이됩니다.

CustomerId,CustomerName분할 점으로 지정하면 dapper는 결과 집합을 3 개의 개체로 분할하려고한다고 가정합니다. 첫 번째는 처음에 시작하고 두 번째는에서 시작하고 CustomerId세 번째는에서 시작합니다 CustomerName.


답변

테이블 이름은 사용자 이름과 유사하며 ‘select *’작업을 사용하여 “CustomerID”와 같은 항목이 두 번 반환 될 수 있습니다. 따라서 Dapper는 작업을 수행하지만 열이 다음과 같기 때문에 너무 일찍 (아마도) 분할됩니다.

(select * might return):
ProductID,
ProductName,
CustomerID, --first CustomerID
AccountOpened,
CustomerID, --second CustomerID,
CustomerName.

이로 인해 spliton : 매개 변수가 그다지 유용하지 않습니다. 특히 열이 어떤 순서로 반환되는지 확실하지 않을 때 유용합니다. 물론 열을 수동으로 지정할 수 있습니다.하지만 2017 년이고 기본 개체 가져 오기에 대해 더 이상 그렇게하는 경우는 거의 없습니다.

우리가하는 일은 수년 동안 수천 개의 쿼리에서 훌륭하게 작동했습니다. 단순히 Id에 별칭을 사용하고 스플릿 온을 지정하지 않는 것입니다 (Dapper의 기본 ‘Id’사용).

select
p.*,

c.CustomerID AS Id,
c.*

… 짜잔! Dapper는 기본적으로 Id에서만 분할되며 해당 Id는 모든 고객 열보다 먼저 발생합니다. 물론 반환 결과 집합에 추가 열을 추가하지만 어떤 열이 어떤 개체에 속하는지 정확히 아는 추가 유틸리티에 대한 오버 헤드는 극히 최소화됩니다. 그리고 이것을 쉽게 확장 할 수 있습니다. 주소와 국가 정보가 필요하십니까?

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

무엇보다도 어떤 열이 어떤 개체와 연결되어 있는지 최소한의 SQL로 명확하게 표시하고 있습니다. 나머지는 Dapper가 처리합니다.


답변

다음 구조를 가정하면 ‘|’ 분할 지점이고 T는 매핑을 적용해야하는 엔티티입니다.

       TFirst         TSecond         TThird           TFourth
------------------+-------------+-------------------+------------
col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8
------------------+-------------+-------------------+------------

다음은 작성해야 할 깔끔한 쿼리입니다.

Query<TFirst, TSecond, TThird, TFourth, TResut> (
    sql : query,
    map: Func<TFirst, TSecond, TThird, TFourth, TResut> func,
    parma: optional,
    splitOn: "col_3, col_n, col_A, col_9")

따라서 TFirst가 col_1 col_2 col_3을 매핑하고 TSecond의 경우 col_n col_m …

splitOn 표현식은 다음과 같이 변환됩니다.

‘col_3’로 명명되거나 별칭이 지정된 열을 찾을 때까지 모든 열을 TFrist로 매핑하고 매핑 결과에 ‘col_3’도 포함합니다.

그런 다음 ‘col_n’에서 시작하는 모든 열을 TSecond로 매핑을 시작하고 새 구분 기호가 발견 될 때까지 매핑을 계속합니다.이 경우에는 ‘col_A’이고 TThird 매핑의 시작을 표시합니다.

SQL 쿼리의 열과 매핑 개체의 소품은 1 : 1 관계 (동일한 이름이어야 함)에 있으며, SQL 쿼리의 결과 열 이름이 다른 경우 ‘AS [ Some_Alias_Name] ‘표현식입니다.


답변

주의 사항이 하나 더 있습니다. CustomerId 필드가 null 인 경우 (일반적으로 왼쪽 조인 쿼리에서) Dapper는 Customer = null로 ProductItem을 만듭니다. 위의 예에서 :

var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(null as decimal) CustomerId, 'n' CustomerName";
var item = connection.Query<ProductItem, Customer, ProductItem>(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();
Debug.Assert(item.Customer == null); 

그리고 한 가지 더주의 / 함정. splitOn에 지정된 필드를 매핑하지 않고 해당 필드에 null Dapper가 포함되어 관련 개체 (이 경우 고객)를 만들고 채 웁니다. 이 클래스를 이전 SQL과 함께 사용하는 방법을 보여주기 위해 :

public class Customer
{
    //public decimal CustomerId { get; set; }
    public string CustomerName { get; set; }
}
...
Debug.Assert(item.Customer != null);
Debug.Assert(item.Customer.CustomerName == "n");  


답변

나는 일반적으로 내 저장소에서 이것을 수행하고 사용 사례에 적합합니다. 나는 나눌 것이라고 생각했다. 어쩌면 누군가가 이것을 더 확장 할 것입니다.

몇 가지 단점은 다음과 같습니다.

  • 이것은 외래 키 속성이 하위 개체의 이름 + “Id”(예 : UnitId)라고 가정합니다.
  • 1 개의 자식 개체 만 부모에 매핑합니다.

코드:

    public IEnumerable<TParent> GetParentChild<TParent, TChild>()
    {
        var sql = string.Format(@"select * from {0} p
        inner join {1} c on p.{1}Id = c.Id",
        typeof(TParent).Name, typeof(TChild).Name);

        Debug.WriteLine(sql);

        var data = _con.Query<TParent, TChild, TParent>(
            sql,
            (p, c) =>
            {
                p.GetType().GetProperty(typeof (TChild).Name).SetValue(p, c);
                return p;
            },
            splitOn: typeof(TChild).Name + "Id");

        return data;
    }


답변

큰 엔터티를 매핑해야하는 경우 각 필드는 어려운 작업이어야합니다.

나는 @BlackjacketMack 대답을 시도했지만 내 테이블 중 하나에 Id 열이 있지만 DB 디자인 문제라는 것을 알고 있지만 … 이는 dapper에 추가 분할을 삽입합니다.

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

나를 위해 작동하지 않습니다. 그럼 난 이것에 약간의 변화 종료, 그냥에서 변경할 경우 수 테이블의 모든 필드와 일치하지 않는 이름을 가진 분리 점 삽입 as Id에 의해 as _SplitPoint_이 같은 최종 SQL 스크립트 외모 :

select
p.*,

c.CustomerID AS _SplitPoint_,
c.*,

address.AddressID AS _SplitPoint_,
address.*,

country.CountryID AS _SplitPoint_,
country.*

그런 다음 dapper에서 하나의 splitOn 만 추가하십시오.

cmd =
    "SELECT Materials.*, " +
    "   Product.ItemtId as _SplitPoint_," +
    "   Product.*, " +
    "   MeasureUnit.IntIdUM as _SplitPoint_, " +
    "   MeasureUnit.* " +
    "FROM   Materials INNER JOIN " +
    "   Product ON Materials.ItemtId = Product.ItemtId INNER JOIN " +
    "   MeasureUnit ON Materials.IntIdUM = MeasureUnit.IntIdUM " +
List < Materials> fTecnica3 = (await dpCx.QueryAsync<Materials>(
        cmd,
        new[] { typeof(Materials), typeof(Product), typeof(MeasureUnit) },
        (objects) =>
        {
            Materials mat = (Materials)objects[0];
            mat.Product = (Product)objects[1];
            mat.MeasureUnit = (MeasureUnit)objects[2];
            return mat;
        },
        splitOn: "_SplitPoint_"
    )).ToList();


답변