이 게시물에서는 고도로 정규화 된 SQL 데이터베이스 를 쿼리 하고 결과를 고도로 중첩 된 C # POCO 객체 세트에 매핑하는 방법을 보여줍니다 .
성분 :
- 8 줄의 C #.
- 일부 조인을 사용하는 합리적으로 간단한 SQL.
- 두 개의 멋진 라이브러리.
이 문제를 해결할 수있는 통찰력 MicroORM
은 mapping the result back to the POCO Entities
. 따라서 두 개의 개별 라이브러리를 사용합니다.
기본적으로 Dapper 를 사용하여 데이터베이스를 쿼리 한 다음 Slapper.Automapper 를 사용 하여 결과를 POCO 에 직접 매핑합니다.
장점
- 단순성 . 8 줄 미만의 코드입니다. 이해, 디버그 및 변경하기가 훨씬 더 쉽습니다.
- 적은 코드 . 몇 줄의 코드는 모두 Slapper입니다. Automapper 는 복잡한 중첩 POCO (즉, POCO가를 포함
List<MyClass1>
하는 POCO 포함)가 있더라도 사용자가 던지는 모든 것을 처리해야합니다 List<MySubClass2>
.
- 속도 . 이 두 라이브러리는 모두 수작업으로 조정 된 ADO.NET 쿼리만큼 빠르게 실행할 수 있도록 엄청난 양의 최적화 및 캐싱 기능을 갖추고 있습니다.
- 우려의 분리 . MicroORM을 다른 것으로 변경할 수 있으며 매핑은 여전히 작동하며 그 반대도 마찬가지입니다.
- 유연성 . Slapper.Automapper 는 임의로 중첩 된 계층 구조를 처리하며 두 단계의 중첩 수준으로 제한되지 않습니다. 우리는 쉽게 빠르게 변경할 수 있으며 모든 것이 여전히 작동합니다.
- 디버깅 . 먼저 SQL 쿼리가 제대로 작동하는지 확인한 다음 SQL 쿼리 결과가 대상 POCO 엔터티에 다시 올바르게 매핑되었는지 확인할 수 있습니다.
- SQL의 개발 용이성 .
inner joins
플랫 결과를 반환하기 위해 플랫 쿼리를 만드는 것이 클라이언트 측에서 스티칭을 사용하여 여러 select 문을 만드는 것보다 훨씬 쉽다는 것을 알았습니다.
- SQL에서 최적화 된 쿼리 . 고도로 정규화 된 데이터베이스에서 플랫 쿼리를 생성하면 SQL 엔진이 전체에 고급 최적화를 적용 할 수 있습니다. 이는 많은 작은 개별 쿼리가 구성되고 실행되는 경우 일반적으로 가능하지 않습니다.
- 신뢰 . Dapper는 StackOverflow의 백엔드이며 Randy Burden은 약간의 슈퍼 스타입니다. 더 말할 필요가 있습니까?
- 개발 속도. 여러 수준의 중첩을 사용하여 매우 복잡한 쿼리를 수행 할 수 있었고 개발 시간이 상당히 짧았습니다.
- 더 적은 버그. 나는 한 번 썼고 방금 효과가 있었고이 기술은 이제 FTSE 회사에 힘을 실어주고 있습니다. 코드가 너무 적어 예기치 않은 동작이 없었습니다.
단점
- 1,000,000 개 이상의 행이 반환되었습니다. 100,000 개 미만의 행을 반환 할 때 잘 작동합니다. 그러나 1,000,000 개 이상의 행을 다시 가져 오는 경우, 우리와 SQL 서버 간의 트래픽을 줄이기 위해이를 사용하여 평면화해서는 안됩니다
inner join
(중복을 가져옴). 대신 여러 select
문을 사용 하고 모든 것을 다시 연결해야합니다. 클라이언트 측 (이 페이지의 다른 답변 참조).
- 이 기술은 쿼리 지향적 입니다. 이 기술을 데이터베이스에 쓰는 데 사용하지는 않았지만 StackOverflow 자체가 Dapper를 DAL (Data Access Layer)로 사용하기 때문에 Dapper는 추가 작업을 통해이 작업을 수행 할 수있는 것 이상이라고 확신합니다.
성능 시험
내 테스트에서 Slapper.Automapper 는 Dapper가 반환 한 결과에 약간의 오버 헤드를 추가했습니다. 이는 여전히 Entity Framework보다 10 배 더 빠르며 조합은 여전히 SQL + C #이 할 수있는 이론적 최대 속도에 가깝습니다 .
대부분의 실제 경우 대부분의 오버 헤드는 C # 측에서 결과를 매핑하는 것이 아니라 최적화되지 않은 SQL 쿼리에 있습니다.
성능 테스트 결과
총 반복 횟수 : 1000
Dapper by itself
: 쿼리 당 1.889 밀리 초, 3 lines of code to return the dynamic
.
Dapper + Slapper.Automapper
: 쿼리 당 2.463 밀리 초, 추가 3 lines of code for the query + mapping from dynamic to POCO Entities
.
작동 예
이 예에서 우리는의 목록을 가지고 Contacts
있고 각각 Contact
은 하나 이상의 phone numbers
.
POCO 법인
public class TestContact
{
public int ContactID { get; set; }
public string ContactName { get; set; }
public List<TestPhone> TestPhones { get; set; }
}
public class TestPhone
{
public int PhoneId { get; set; }
public int ContactID { get; set; }
public string Number { get; set; }
}
SQL 테이블 TestContact
SQL 테이블 TestPhone
이 테이블에는 테이블을 참조하는 외래 키 ContactID
가 있습니다 TestContact
( List<TestPhone>
위의 POCO에 해당 ).
플랫 결과를 생성하는 SQL
SQL 쿼리에서는 JOIN
필요한 모든 데이터를 비정규 화 된 형식 으로 가져 오는 데 필요한 만큼 많은 문을 사용 합니다 . 예, 이것은 출력에 중복을 생성 할 수 있지만 이러한 중복은 Slapper.Automapper 를 사용 하여이 쿼리의 결과를 POCO 객체 맵에 자동으로 매핑 할 때 자동으로 제거됩니다 .
USE [MyDatabase];
SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId
C # 코드
const string sql = @"SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId";
string connectionString =
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
{
dynamic test = conn.Query<dynamic>(sql);
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestContact), new List<string> { "ContactID" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestPhone), new List<string> { "PhoneID" });
var testContact = (Slapper.AutoMapper.MapDynamic<TestContact>(test) as IEnumerable<TestContact>).ToList();
foreach (var c in testContact)
{
foreach (var p in c.TestPhones)
{
Console.Write("ContactName: {0}: Phone: {1}\n", c.ContactName, p.Number);
}
}
}
}
산출
POCO 엔티티 계층
Visual Studio에서 보면, 우리는 우리가이 즉, Slapper.Automapper 제대로 우리의 POCO 엔터티를 채워 것을 볼 수 있습니다 List<TestContact>
, 각각 TestContact
A가 들어 List<TestPhone>
.
메모
Dapper와 Slapper.Automapper는 속도를 위해 모든 것을 내부적으로 캐시합니다. 메모리 문제가 발생할 가능성이 거의없는 경우 두 가지 모두에 대한 캐시를 가끔 지워야합니다.
결과를 POCO 엔터티에 매핑하는 방법에 대한 단서를 Slapper.Automapper에 제공하기 위해 밑줄 ( _
) 표기법 을 사용하여 다시 오는 열의 이름을 지정해야합니다 .
각 POCO 엔티티의 기본 키에 대한 Slapper.Automapper 단서를 제공해야합니다 (라인 참조 Slapper.AutoMapper.Configuration.AddIdentifiers
). 이를 Attributes
위해 POCO에서 사용할 수도 있습니다 . 이 단계를 건너 뛰면 Slapper.Automapper가 매핑을 올바르게 수행하는 방법을 알지 못하므로 이론적으로 잘못 될 수 있습니다.
2015-06-14 업데이트
이 기술을 정규화 된 40 개 이상의 테이블이있는 대규모 프로덕션 데이터베이스에 성공적으로 적용했습니다. 그것은 16 이상과 고급 SQL 쿼리를 매핑 완벽하게 작동 inner join
하고 left join
적절한 POCO 계층에 (중첩의 4 개 수준). 쿼리는 ADO.NET에서 직접 코딩하는 것만 큼 빠릅니다 (일반적으로 쿼리의 경우 52 밀리 초, 플랫 결과에서 POCO 계층 구조로 매핑하는 경우 50 밀리 초). 이것은 실제로 혁신적인 것은 아니지만 속도와 사용 편의성면에서 Entity Framework를 능가합니다. 특히 우리가하는 모든 작업이 쿼리를 실행하는 경우에는 더욱 그렇습니다.
업데이트 2016-02-19
코드는 9 개월 동안 프로덕션에서 완벽하게 실행되었습니다. 의 최신 버전 Slapper.Automapper
에는 SQL 쿼리에서 반환되는 null과 관련된 문제를 해결하기 위해 적용한 모든 변경 사항이 있습니다.
업데이트 2017-02-20
코드는 21 개월 동안 프로덕션에서 완벽하게 실행되었으며 FTSE 250 회사에서 수백 명의 사용자의 지속적인 쿼리를 처리했습니다.
Slapper.Automapper
.csv 파일을 POCO 목록에 직접 매핑하는데도 좋습니다. .csv 파일을 IDictionary 목록으로 읽어 들인 다음 대상 POCO 목록에 직접 매핑합니다. 유일한 트릭은 속성을 추가하고 int Id {get; set}
모든 행에 대해 고유한지 확인해야한다는 것입니다 (그렇지 않으면 자동 매퍼가 행을 구분할 수 없습니다).
업데이트 2019-01-29
더 많은 코드 주석을 추가하기위한 사소한 업데이트.
참조 : https://github.com/SlapperAutoMapper/Slapper.AutoMapper