Dapper의 사용자 정의 매핑

.net c# dapper sql-server

문제

페이징 된 결과를 얻기 위해 Dapper 및 다중 매핑이있는 CTE를 사용하려고합니다. 중복 된 열로 인해 불편을 겪고 있습니다. CTE는 예를 들어 이름 열을 갖지 못하게합니다.

다음 쿼리를 열 이름과 속성 간의 불일치가 아니라 다음 개체에 매핑하고 싶습니다.

질문:

WITH TempSites AS(
    SELECT
        [S].[SiteID],
        [S].[Name] AS [SiteName],
        [S].[Description],
        [L].[LocationID],
        [L].[Name] AS [LocationName],
        [L].[Description] AS [LocationDescription],
        [L].[SiteID] AS [LocationSiteID],
        [L].[ReportingID]
    FROM (
        SELECT * FROM [dbo].[Sites] [1_S]
        WHERE [1_S].[StatusID] = 0
        ORDER BY [1_S].[Name]
        OFFSET 10 * (1 - 1) ROWS
        FETCH NEXT 10 ROWS ONLY
    ) S
        LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)

SELECT *
FROM TempSites, MaxItems

사물:

public class Site
{
    public int SiteID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public List<Location> Locations { get; internal set; }
}

public class Location
{
    public int LocationID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Guid ReportingID { get; set; }
    public int SiteID { get; set; }
}

어떤 이유로 나는 내 머리 속에이 시나리오를 처리 할 명명 규칙이 있지만 문서에서 언급을 찾을 수 없다.

수락 된 답변

하나 이상의 문제점이 있습니다. 하나씩 문제를 해결하십시오.

CTE 중복 열 이름 :

CTE는 중복 된 열 이름을 허용하지 않으므로 가급적이면 쿼리 시도와 같은 일부 명명 규칙을 사용하여 별칭을 사용하여 문제를 해결해야합니다.

어떤 이유로 나는 내 머리 속에이 시나리오를 처리 할 명명 규칙이 있지만 문서에서 언급을 찾을 수 없다.

아마도 DefaultTypeMap.MatchNamesWithUnderscores 속성을 true 설정했지만 속성의 코드 문서로 다음과 같이 생각했을 것입니다.

User_Id와 같은 열 이름은 UserId와 같은 속성 / 필드와 일치 할 수 있습니까?

분명히 이것은 해결책이 아닙니다. 그러나이 문제는 "{prefix}{propertyName}" 같은 사용자 지정 명명 규칙을 도입하면 쉽게 해결할 수 있습니다 (기본 접두어는 "{className}_" ). Dapper의 CustomPropertyTypeMap 통해 구현합니다. 다음은이를 수행하는 도우미 메소드입니다.

public static class CustomNameMap
{
    public static void SetFor<T>(string prefix = null)
    {
        if (prefix == null) prefix = typeof(T).Name + "_";
        var typeMap = new CustomPropertyTypeMap(typeof(T), (type, name) =>
        {
            if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                name = name.Substring(prefix.Length);
            return type.GetProperty(name);
        });
        SqlMapper.SetTypeMap(typeof(T), typeMap);
    }
}

이제는 한 번만 호출하면됩니다.

CustomNameMap.SetFor<Location>();

쿼리에 명명 규칙을 적용하십시오.

WITH TempSites AS(
    SELECT
        [S].[SiteID],
        [S].[Name],
        [S].[Description],
        [L].[LocationID],
        [L].[Name] AS [Location_Name],
        [L].[Description] AS [Location_Description],
        [L].[SiteID] AS [Location_SiteID],
        [L].[ReportingID]
    FROM (
        SELECT * FROM [dbo].[Sites] [1_S]
        WHERE [1_S].[StatusID] = 0
        ORDER BY [1_S].[Name]
        OFFSET 10 * (1 - 1) ROWS
        FETCH NEXT 10 ROWS ONLY
    ) S
        LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)

SELECT *
FROM TempSites, MaxItems

당신은 그 부분으로 끝납니다. 물론 원하는 경우 "Loc_"와 같이 짧은 접두어를 사용할 수 있습니다.

쿼리 결과를 제공된 클래스에 매핑 :

이 특별한 경우에는 Func<TFirst, TSecond, TReturn> map delegate를 전달하고 splitOn 매개 변수를 결합하여 LocationID 를 분할 열로 지정할 수있는 Query 메서드 오버로드를 사용해야합니다. 그러나 그것은 충분하지 않습니다. Dapper의 다중 매핑 기능을 사용하면 Location 가있는 Site 목록 (LINQ GroupJoin )이 필요한 동안 단일 행을 여러 개의 단일 객체 (예 : LINQ Join )로 분할 할 수 있습니다.

Query 메서드를 사용하여 임시 익명 형식으로 프로젝트를 생성 한 다음 정규 LINQ를 사용하여 다음과 같이 원하는 출력을 생성 할 수 있습니다.

var sites = cn.Query(sql, (Site site, Location loc) => new { site, loc }, splitOn: "LocationID")
    .GroupBy(e => e.site.SiteID)
    .Select(g =>
    {
        var site = g.First().site;
        site.Locations = g.Select(e => e.loc).Where(loc => loc != null).ToList();
        return site;
    })
    .ToList();

여기서 cn 은 열려진 SqlConnection 이고 sql 은 위 쿼리를 포함하는 string 입니다.


인기 답변

ColumnAttributeTypeMapper를 사용하여 열 이름을 다른 속성과 매핑 할 수 있습니다.

자세한 내용은 Gist에 대한 첫 번째 의견을 참조하십시오.

다음과 같은 매핑을 할 수 있습니다.

public class Site
{
    public int SiteID { get; set; }
    [Column("SiteName")]
    public string Name { get; set; }
    public string Description { get; set; }
    public List<Location> Locations { get; internal set; }
}

public class Location
{
    public int LocationID { get; set; }
    [Column("LocationName")]
    public string Name { get; set; }
    [Column("LocationDescription")]
    public string Description { get; set; }
    public Guid ReportingID { get; set; }
    [Column("LocationSiteID")]
    public int SiteID { get; set; }
}

매핑은 다음 세 가지 방법 중 하나를 사용하여 수행 할 수 있습니다.

방법 1

모델에 대한 사용자 지정 TypeMapper를 다음과 같이 수동으로 설정합니다.

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

방법 2

.NET Framework> = v4.0의 클래스 라이브러리의 경우 PreApplicationStartMethod를 사용하여 클래스를 사용자 정의 유형 매핑에 등록 할 수 있습니다.

using System.Web;
using Dapper;

[assembly: PreApplicationStartMethod(typeof(YourNamespace.Initiator), "RegisterModels")]

namespace YourNamespace
{
    public class Initiator
    {
        private static void RegisterModels()
        {
             SqlMapper.SetTypeMap(typeof(Site), new ColumnAttributeTypeMapper<Site>());
             SqlMapper.SetTypeMap(typeof(Location), new ColumnAttributeTypeMapper<Location>());
             // ...
        }
    }
}

방법 3

또는 ColumnAttribute가 리플렉션 및 유형 매핑을 통해 적용되는 클래스를 찾을 수 있습니다. 이 작업은 약간 느려질 수 있지만 어셈블리의 모든 매핑이 자동으로 수행됩니다. 어셈블리가로드되면 RegisterTypeMaps() 호출하기 만하면됩니다.

    public static void RegisterTypeMaps()
    {
        var mappedTypes = Assembly.GetAssembly(typeof (Initiator)).GetTypes().Where(
            f =>
            f.GetProperties().Any(
                p =>
                p.GetCustomAttributes(false).Any(
                    a => a.GetType().Name == ColumnAttributeTypeMapper<dynamic>.ColumnAttributeName)));

        var mapper = typeof(ColumnAttributeTypeMapper<>);
        foreach (var mappedType in mappedTypes)
        {
            var genericType = mapper.MakeGenericType(new[] { mappedType });
            SqlMapper.SetTypeMap(mappedType, Activator.CreateInstance(genericType) as SqlMapper.ITypeMap);
        }
    }


아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow
이 KB는 합법적입니까? 예, 이유를 알아보십시오.
아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow
이 KB는 합법적입니까? 예, 이유를 알아보십시오.