Dapper.Net에서 일대일 쿼리를 어떻게 작성합니까?

.net c# dapper

문제

이 코드를 작성하여 일대 다 관계로 프로젝트를 만들었지 만 작동하지 않습니다.

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.
  • 두 가지 훌륭한 라이브러리.

이 문제를 해결할 수있는 통찰력은 MicroORMmapping the result back to the POCO Entities 것을 분리하는 MicroORM 입니다. 따라서 두 개의 별도 라이브러리를 사용합니다.

본질적으로 Dapper 를 사용하여 데이터베이스를 쿼리 한 다음 Slapper.Automapper 를 사용하여 결과를 POCO 에 직접 매핑합니다.

장점

  • 단순함 . 8 줄 미만의 코드. 나는 이것을 이해하기 쉽고, 디버그하고, 변경하는 것이 훨씬 쉽다는 것을 알았다.
  • 적은 코드 . 몇 줄의 코드가 전부입니다 Slapper.Automapper은 우리가 복잡한 중첩 POCO이 있더라도, 당신이 그것에 던질 아무것도 처리해야합니다 (즉, POCO에 포함 된 List<MyClass1> 다시 포함하는 List<MySubClass2> 등).
  • 속도 . 이 두 라이브러리는 손으로 튜닝 한 ADO.NET 쿼리만큼 빠르게 실행할 수 있도록 특별한 양의 최적화와 캐싱을 제공합니다.
  • 우려의 분리 . 우리는 MicroORM을 다른 것으로 변경할 수 있으며 매핑은 여전히 작동하며 그 반대도 마찬가지입니다.
  • 유연성 . Slapper.Automapper 는 임의로 중첩 된 계층을 처리하지만 두 단계의 중첩 수준에만 국한되지 않습니다. 우리는 쉽게 빠른 변경을 할 수 있으며 모든 것이 여전히 작동합니다.
  • 디버깅 . 먼저 SQL 쿼리가 제대로 작동하는지 확인한 다음 SQL 쿼리 결과가 대상 POCO 엔터티에 올바르게 매핑되었는지 확인할 수 있습니다.
  • SQL 개발이 쉽습니다 . 평면 결과를 반환하는 inner joins 을 사용하여 병합 된 쿼리를 만드는 것이 클라이언트 쪽에서 스티칭 (stitching)을 사용하여 여러 select 문을 만드는 것보다 훨씬 쉽습니다.
  • SQL에서 최적화 된 쿼리 . 고도로 정규화 된 데이터베이스에서 플랫 쿼리를 생성하면 SQL 엔진이 다수의 작은 개별 쿼리가 생성되어 실행될 경우 일반적으로 불가능한 전체에 고급 최적화를 적용 할 수 있습니다.
  • 신뢰 . Dapper는 StackOverflow의 백 엔드이며, Randy Burden은 약간의 슈퍼 스타입니다. 나는 더 이상 말할 필요가 있니?
  • 개발 속도. 여러 수준의 중첩을 사용하여 매우 복잡한 쿼리를 수행 할 수 있었고 dev 시간은 상당히 낮았습니다.
  • 적은 버그. 나는 그것을 한 번 썼는데, 이제는 효과가 있었고,이 기법은 이제 FTSE 회사에 힘을주고 있습니다. 예기치 않은 동작이 거의 발생하지 않았습니다.

단점

  • 1,000,000 개를 초과하는 확장이 반환되었습니다. <100,000 행을 반환 할 때 잘 작동합니다. 그러나 우리가 SQL 서버와의 트래픽을 줄이기 위해 1,000,000 개가 넘는 행을 다시 가져 오는 경우 inner join 을 사용하여이를 병합해서는 안됩니다 (중복을 다시 가져옵니다). 대신 여러 개의 select 문을 사용하고 모든 것을 다시 스티칭해야합니다 (이 페이지의 다른 답변을 참조하십시오).
  • 이 기술은 쿼리 지향적 입니다. 필자는이 기술을 데이터베이스에 쓰지 않았지만, StackOverflow 자체가 Dapper를 데이터 액세스 레이어 (DAL)로 사용하기 때문에 Dapper가 더 많은 작업을 수행 할 수있는 능력 이상이라고 확신합니다.

성능 시험

내 테스트에서 Slapper.Automapper 는 Dapper가 반환 한 결과에 약간의 오버 헤드가 추가되어 여전히 Entity Framework보다 10 배 더 빠르다는 것을 의미했습니다 . 조합은 SQL + C #이 가능한 이론적 인 최대 속도와 거의 비슷합니다 .

대부분의 실제적인 경우 오버 헤드의 대부분은 최적의 SQL 쿼리보다 적지 만 C # 측의 결과 매핑이 아닙니다.

성능 테스트 결과

총 반복 횟수 : 1000

  • Dapper by itself : 조회 당 1.889 밀리 초, 3 lines of code to return the dynamic 사용 3 lines of code to return the dynamic .
  • Dapper + Slapper.Automapper : 쿼리 당 +4664 밀리 초, 3 lines of code for the query + mapping from dynamic to POCO Entities 추가 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

이 테이블에는 TestContact 테이블 (위의 POCO의 List<TestPhone> 에 해당)을 참조하는 외부 키 ContactID 가 있습니다.

여기에 이미지 설명을 입력하십시오.

플랫 결과를 생성하는 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 = // -- 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 에는 List<TestPhone> 있습니다.

여기에 이미지 설명을 입력하십시오.

노트

모두 Dapper와 Slapper.Automapper는 내부적으로 속도를 위해 모든 것을 캐시합니다. 메모리 문제가있는 경우 (거의 발생하지 않음) 두 경우 모두 캐시를 지우는 것을 잊지 마십시오.

밑줄 ( _ ) 표기법 을 사용하여 결과를 POCO 엔티티에 매핑하는 방법에 대한 Slapper.Automapper 단서를 제공하는 열의 이름을 지정했는지 확인하십시오.

각 POCO 엔티티의 기본 키에 Slapper.Automapper 단서를 제공해야합니다 ( Slapper.AutoMapper.Configuration.AddIdentifiers 행 참조). 이 경우 POCO에서 Attributes 을 사용할 수도 있습니다. 이 단계를 건너 뛰면 Slapper.Automapper가 매핑을 올바르게 수행하는 방법을 알지 못하기 때문에 이론적으로 잘못 될 수 있습니다.

2015-06-14 업데이트

성공적으로이 기술을 40 개가 넘는 정규화 된 테이블이있는 거대한 프로덕션 데이터베이스에 적용했습니다. 고급 SQL 쿼리를 16 개 이상의 inner join 으로 매핑하고 적절한 POCO 계층 구조에 네 개의 left join 레벨로 left join 하여 완벽하게 작업했습니다. 쿼리는 ADO.NET에서 손으로 코딩하는 것만 큼 빨라지고 (쿼리의 경우 일반적으로 52 밀리 초이고 플랫 결과에서 POCO 계층에 매핑하는 경우 50 밀리 초) 빠른 속도로 수행됩니다. 이것은 실제로 혁명적 인 것은 아닙니다만, 특히 우리가 수행하는 모든 작업이 쿼리를 실행하는 경우 엔, Entity Framework가 속도와 사용 편의성을 확실하게 능가합니다.

2016-02-19 업데이트

코드는 생산 과정에서 9 개월 동안 완벽하게 실행되었습니다. Slapper.Automapper 의 최신 버전에는 SQL 쿼리에서 반환되는 null과 관련된 문제를 해결하기 위해 적용한 모든 변경 사항이 있습니다.

2017-02-20 업데이트

코드는 21 개월 동안 생산 단계에서 완벽하게 실행되었으며 FTSE 250 회사의 수백 명의 사용자로부터 지속적인 쿼리를 처리했습니다.

Slapper.Automapper 는 .csv 파일을 Slapper.Automapper 목록에 바로 매핑하는 데 Slapper.Automapper 합니다. .csv 파일을 IDictionary 목록으로 읽어서 POCO의 대상 목록에 바로 매핑하십시오. 유일한 트릭은 당신이 propery 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();
}

난 여전히 하나의 데이터베이스에 전화를 할, 나는 지금 하나 대신 2 쿼리를 실행하는 동안 두 번째 쿼리는 덜 최적의 LEFT 조인 대신 INNER 조인을 사용하고 있습니다.



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