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

.net c# dapper

문제

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

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>();
        }
    }

아무도 실수를 발견 할 수 있습니까?

편집하다:

이들은 나의 실체입니다 :

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>();
        }
    }

편집하다:

내가 쿼리를 변경 :

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<Employee> 에 대한 첫 번째 쿼리에서 어떤 문제가 있었는지 확실하지 않다.

수락 된 답변

이 게시물은 고도로 정규화 된 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> 다시 포함하는 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 문을 사용 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 : 검색어 당 3 lines of code for the query + mapping from dynamic to POCO Entities 사용하여 쿼리 당 2.463 밀리 초, 3 lines of code for the query + mapping from dynamic to POCO Entities .

근무한 예

이 예에서는 Contacts 목록이 있고 각 Contact 에는 하나 이상의 phone numbers 가있을 수 있습니다.

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

SQL 테이블 TestContact

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

SQL 테이블 TestPhone

이 테이블에는 TestContact 테이블 (위의 POCO의 List<TestPhone> 에 해당)을 참조하는 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 # 코드

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

산출

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

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} 하고 모든 행에 대해 고유한지 확인하십시오 (그렇지 않으면 자동 선택기가 행을 구별하지 못합니다).

참조 : 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는 합법적입니까? 예, 이유를 알아보십시오.