Dapper.Net에서 일대 다 쿼리를 어떻게 작성합니까?
일대 다 관계를 프로젝트하기 위해 코드를 작성했지만 작동하지 않습니다.
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
IEnumerable<Store> stores = connection.Query<Store, IEnumerable<Employee>, Store>
(@"Select Stores.Id as StoreId, Stores.Name,
Employees.Id as EmployeeId, Employees.FirstName,
Employees.LastName, Employees.StoreId
from Store Stores
INNER JOIN Employee Employees ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; },
splitOn: "EmployeeId");
foreach (var store in stores)
{
Console.WriteLine(store.Name);
}
}
누가 실수를 발견 할 수 있습니까?
편집하다 :
다음은 내 가수입니다.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public IList<Store> Stores { get; set; }
public Product()
{
Stores = new List<Store>();
}
}
public class Store
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Employee> Employees { get; set; }
public Store()
{
Products = new List<Product>();
Employees = new List<Employee>();
}
}
편집하다 :
쿼리를 다음과 같이 변경합니다.
IEnumerable<Store> stores = connection.Query<Store, List<Employee>, Store>
(@"Select Stores.Id as StoreId ,Stores.Name,Employees.Id as EmployeeId,Employees.FirstName,
Employees.LastName,Employees.StoreId from Store Stores INNER JOIN Employee Employees
ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; }, splitOn: "EmployeeId");
그리고 나는 예외를 제거합니다! 그러나 직원은 전혀 매핑되지 않습니다. 나는 여전히 IEnumerable<Employee>
첫 번째 쿼리에서 어떤 문제가 있었는지 잘 모르겠습니다 .
이 게시물은 고도로 정규화 된 SQL 데이터베이스 를 쿼리 하고 결과를 고도로 중첩 된 C # POCO 객체 세트에 매핑하는 방법을 보여줍니다 .
성분 :
- 8 줄의 C #.
- 일부 조인을 사용하는 합리적으로 간단한 SQL.
- 두 개의 멋진 라이브러리.
이 문제를 해결 수있는 MicroORM
은 mapping the result back to the POCO Entities
. 따라서 두 개의 개인 식별 라이브러리를 사용합니다.
- MicroORM 독립의 Dapper .
- 슬 래퍼 . 매핑을위한 Automapper .
기본적으로 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
결과를 반환하기 위해 플랫 쿼리를 만드는 것이 클라이언트 측에서 스티칭을 사용하여 여러 선택 문을 만드는 것보다 훨씬 많은 것을 알았습니다. - 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; } // foreign key
public string Number { get; set; }
}
SQL 테이블 TestContact
SQL 테이블 TestPhone
이 테이블에는 테이블을 참조하는 외래 키 ContactID
가 있습니다 TestContact
( List<TestPhone>
위의 POCO에 해당 ).
플랫 결과를 생성하는 SQL
SQL 쿼리에서는 JOIN
필요한 모든 데이터를 비정규 화 된 형태 로 가져 오는 데 필요한 만큼 많은 문을 사용 합니다 . 예, 이것은 출력에 중복을 생성 할 수 있지만 이러한 중복은 Slapper.Automapper 를 사용 하여이 쿼리의 결과를 마시고 맵에 자동 object-으로 매핑 할 때 자동으로 제거됩니다 .
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 = // -- Insert SQL connection string here.
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
// Can set default database here with conn.ChangeDatabase(...)
{
// Step 1: Use Dapper to return the flat result as a Dynamic.
dynamic test = conn.Query<dynamic>(sql);
// Step 2: Use Slapper.Automapper for mapping to the POCO Entities.
// - IMPORTANT: Let Slapper.Automapper know how to do the mapping;
// let it know the primary key for each POCO.
// - Must also use underscore notation ("_") to name parameters in the SQL query;
// see Slapper.Automapper docs.
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
가능한 한 간단하게 유지하고 싶었습니다.
public List<ForumMessage> GetForumMessagesByParentId(int parentId)
{
var sql = @"
select d.id_data as Id, d.cd_group As GroupId, d.cd_user as UserId, d.tx_login As Login,
d.tx_title As Title, d.tx_message As [Message], d.tx_signature As [Signature], d.nm_views As Views, d.nm_replies As Replies,
d.dt_created As CreatedDate, d.dt_lastreply As LastReplyDate, d.dt_edited As EditedDate, d.tx_key As [Key]
from
t_data d
where d.cd_data = @DataId order by id_data asc;
select d.id_data As DataId, di.id_data_image As DataImageId, di.cd_image As ImageId, i.fl_local As IsLocal
from
t_data d
inner join T_data_image di on d.id_data = di.cd_data
inner join T_image i on di.cd_image = i.id_image
where d.id_data = @DataId and di.fl_deleted = 0 order by d.id_data asc;";
var mapper = _conn.QueryMultiple(sql, new { DataId = parentId });
var messages = mapper.Read<ForumMessage>().ToDictionary(k => k.Id, v => v);
var images = mapper.Read<ForumMessageImage>().ToList();
foreach(var imageGroup in images.GroupBy(g => g.DataId))
{
messages[imageGroup.Key].Images = imageGroup.ToList();
}
return messages.Values.ToList();
}
나는 여전히 데이터베이스에 대한 한 번의 호출을 수행하고 이제 하나 대신 두 개의 쿼리를 실행하는 동안 두 번째 쿼리는 덜 최적의 LEFT 조인 대신 INNER 조인을 사용합니다.
이 답변 에 따르면 Dapper.Net에는 일대 다 매핑 지원이 없습니다. 쿼리는 항상 데이터베이스 행당 하나의 개체를 반환합니다. 하지만 대체 솔루션이 포함되어 있습니다.
대신 Func를 사용하여 부모 키를 선택하는 Andrew의 대답을 약간 수정했습니다 GetHashCode
.
public static IEnumerable<TParent> QueryParentChild<TParent, TChild, TParentKey>(
this IDbConnection connection,
string sql,
Func<TParent, TParentKey> parentKeySelector,
Func<TParent, IList<TChild>> childSelector,
dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
Dictionary<TParentKey, TParent> cache = new Dictionary<TParentKey, TParent>();
connection.Query<TParent, TChild, TParent>(
sql,
(parent, child) =>
{
if (!cache.ContainsKey(parentKeySelector(parent)))
{
cache.Add(parentKeySelector(parent), parent);
}
TParent cachedParent = cache[parentKeySelector(parent)];
IList<TChild> children = childSelector(cachedParent);
children.Add(child);
return cachedParent;
},
param as object, transaction, buffered, splitOn, commandTimeout, commandType);
return cache.Values;
}
사용 예
conn.QueryParentChild<Product, Store, int>("sql here", prod => prod.Id, prod => prod.Stores)
다음은 조잡한 해결 방법입니다.
public static IEnumerable<TOne> Query<TOne, TMany>(this IDbConnection cnn, string sql, Func<TOne, IList<TMany>> property, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var cache = new Dictionary<int, TOne>();
cnn.Query<TOne, TMany, TOne>(sql, (one, many) =>
{
if (!cache.ContainsKey(one.GetHashCode()))
cache.Add(one.GetHashCode(), one);
var localOne = cache[one.GetHashCode()];
var list = property(localOne);
list.Add(many);
return localOne;
}, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
return cache.Values;
}
결코 가장 효율적인 방법은 아니지만 시작하고 실행할 수 있습니다. 기회가 생기면 이것을 최적화하려고 노력할 것입니다.
다음과 같이 사용하십시오.
conn.Query<Product, Store>("sql here", prod => prod.Stores);
객체가 GetHashCode
다음과 같이 구현해야 함을 명심하십시오 .
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
다음은 또 다른 방법입니다.
Order (하나)-OrderDetail (다수)
using (var connection = new SqlCeConnection(connectionString))
{
var orderDictionary = new Dictionary<int, Order>();
var list = connection.Query<Order, OrderDetail, Order>(
sql,
(order, orderDetail) =>
{
Order orderEntry;
if (!orderDictionary.TryGetValue(order.OrderID, out orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
},
splitOn: "OrderDetailID")
.Distinct()
.ToList();
}
출처 : http://dapper-tutorial.net/result-multi-mapping#example---query-multi-mapping-one-to-many
참고 URL : https://stackoverflow.com/questions/9350467/how-do-i-write-one-to-many-query-in-dapper-net
'ProgramingTip' 카테고리의 다른 글
IntelliJ 정적 가져 오기 완료 (0) | 2020.10.31 |
---|---|
Python에서 길이가 같은 여러 목록을 인터리브 (0) | 2020.10.31 |
HierarchyViewer가 Samsung Galaxy TAB 7.0에서 작동하지 않는 이유는 무엇입니까? (0) | 2020.10.31 |
Python에서 PDFMiner를 사용하여 PDF 파일에서 텍스트 추출? (0) | 2020.10.31 |
어디에서 URL을 어떻게 정규화 할 수 있습니까? (0) | 2020.10.31 |