Dapper ORM을 사용하여 ID를 입력 할 때 *에서 * 선택 (…)


231

IN 절의 값 목록이 비즈니스 논리에서 올 때 Dapper ORM을 사용하여 IN 절로 쿼리를 작성하는 가장 좋은 방법은 무엇입니까? 예를 들어 검색어가 있다고 가정 해 보겠습니다.

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

commaSeparatedListOfIDs비즈니스 로직에서 전달되고 있으며 그것은 모든 종류의 수 있습니다 IEnumerable(of Integer). 이 경우 쿼리를 어떻게 구성합니까? 기본적으로 문자열 연결이거나 내가 알지 못하는 일종의 고급 매개 변수 매핑 기술이 있습니까? 지금까지 내가 한 일을해야합니까?

답변:


366

Dapper는 이것을 직접 지원합니다. 예를 들어 ...

string sql = "SELECT * FROM SomeTable WHERE id IN @ids"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});

47
배열에 보낼 수있는 항목 수에는 제한이 있습니다. 너무 많은 ID를 전달할 때 어려운 방법을 깨달았습니다. 정확한 숫자는 기억하지 못하지만 Dapper가 쿼리 작동 / 실행을 중지하기 전에 200 개의 요소라고 생각합니다.
Marko

8
Marko, 그게 중요합니다. 그리고 그렇게하는 경우 ID 목록을 전달하는 대신 조인 또는 조인 방지와 같은 데이터를 쿼리하는 다른 방법을 찾는 것이 좋습니다. IN 절은 성능이 가장 우수한 쿼리가 아니며 종종 exist 절로 대체 될 수 있습니다.
Don Rolling

24
참고-SQL Server 2008 R2에는 IN조항 에 대한 항목 수가 2100 개로 제한되어 있습니다.
Jesse

6
그리고 SQLite는 기본적으로 999 개의 변수로 제한됩니다.
Cameron

8
주의 : SQL Server에서는 배열에 여러 항목이 있고 매개 변수를 괄호로 묶으면 실패합니다. 괄호를 제거하면 문제가 해결됩니다.
ajbeaven

66

GitHub 프로젝트 홈페이지 에서 직접 :

Dapper를 사용하면 IEnumerable을 전달하고 쿼리를 자동으로 매개 변수화합니다.

connection.Query<int>(
    @"select * 
      from (select 1 as Id union all select 2 union all select 3) as X 
      where Id in @Ids", 
    new { Ids = new int[] { 1, 2, 3 });

다음으로 번역됩니다 :

select * 
from (select 1 as Id union all select 2 union all select 3) as X 
where Id in (@Ids1, @Ids2, @Ids3)

// @Ids1 = 1 , @Ids2 = 2 , @Ids2 = 3

43

INMSSQL이 처리 하기에 절이 너무 큰 경우 Dapper와 함께 TableValueParameter를 매우 쉽게 사용할 수 있습니다.

  1. MSSQL에서 TVP 유형을 작성하십시오.

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
  2. 만들기 DataTableTVP 동일한 컬럼 (들) 및 값으로 채우는

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
  3. INNER JOINTVP 테이블 에서 작업을 수행하도록 Dapper 쿼리를 수정하십시오 .

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
  4. Dapper 쿼리 호출에서 DataTable을 전달하십시오.

    sqlConn.Query(query, new {tvp = tvpTable.AsTableValuedParameter("dbo.MyTVP")});

여러 열의 대량 업데이트를 원할 때 환상적으로 작동합니다. 간단히 TVP를 빌드하고 TVP UPDATE에 내부 조인으로 수행하십시오.


그러나 훌륭한 솔루션은 .Net Core에서 작동하지 않습니다. stackoverflow.com/questions/41132350/…을 참조하십시오 . 이 페이지도 참조하십시오 : github.com/StackExchange/Dapper/issues/603
pcdev

3
이것은 우리에게 성능 문제를 해결했기 때문에 be ProviderIdon 을 고려할 수도 있습니다 (전달 한 값에는 중복이 없습니다). MyTVPPRIMARY KEY CLUSTERED
Richardissimo

@Richardissimo 어떻게 그렇게하는지 예를 보여줄 수 있습니까? 구문이 맞지 않는 것 같습니다.
Mike Cole


14

다음은 ID 목록을 사용하여 Dapper로 많은 수의 행을 쿼리하는 가장 빠른 방법입니다. 나는 이것이 당신이 생각할 수있는 거의 모든 다른 방법보다 빠르다고 약속합니다 (다른 답변에서 주어진 TVP를 사용하는 것을 제외하고는 테스트하지 않았지만 여전히 채워야 하기 때문에 속도가 느릴 수 있습니다) TVP). 그것은이다 행성 빠르고 말끔이 사용하는 것보다 IN구문과 우주 빠른 행에 의해 엔티티 프레임 워크의 행보다 더합니다. 그리고 그것은 대륙의 목록 VALUES이나 UNION ALL SELECT항목을 전달하는 것보다 빠릅니다 . 다중 열 키를 사용하도록 쉽게 확장 할 수 있으며 DataTable, 임시 테이블 및 조인 조건에 추가 열을 추가하기 만하면 됩니다.

public IReadOnlyCollection<Item> GetItemsByItemIds(IEnumerable<int> items) {
   var itemList = new HashSet(items);
   if (itemList.Count == 0) { return Enumerable.Empty<Item>().ToList().AsReadOnly(); }

   var itemDataTable = new DataTable();
   itemDataTable.Columns.Add("ItemId", typeof(int));
   itemList.ForEach(itemid => itemDataTable.Rows.Add(itemid));

   using (SqlConnection conn = GetConnection()) // however you get a connection
   using (var transaction = conn.BeginTransaction()) {
      conn.Execute(
         "CREATE TABLE #Items (ItemId int NOT NULL PRIMARY KEY CLUSTERED);",
         transaction: transaction
      );

      new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, transaction) {
         DestinationTableName = "#Items",
         BulkCopyTimeout = 3600 // ridiculously large
      }
         .WriteToServer(itemDataTable);
      var result = conn
         .Query<Item>(@"
            SELECT i.ItemId, i.ItemName
            FROM #Items x INNER JOIN dbo.Items i ON x.ItemId = i.ItemId
            DROP TABLE #Items;",
            transaction: transaction,
            commandTimeout: 3600
         )
         .ToList()
         .AsReadOnly();
      transaction.Rollback(); // Or commit if you like
      return result;
   }
}

벌크 인서트에 대해 약간 배워야합니다. 트리거 발생 (기본값은 아니오), 제한 조건 존중, 테이블 잠금, 동시 삽입 허용 등에 대한 옵션이 있습니다.


예 나는 ID로 임시 테이블을 만든 다음 해당 테이블에서 내부 조인한다는 일반적인 아이디어에 동의합니다. 우리는 이것을 내부적으로 수행했으며 쿼리 성능을 크게 향상 시켰습니다. DataTable 클래스를 사용하여 확실하지는 않지만 솔루션은 완전히 유효합니다. 이것은 훨씬 빠른 방법입니다.
Marko

DataTable벌크 삽입이 필요합니다. 어떻게 당신은 임시 테이블에 50,000 값을 삽입?
ErikE

1
한도를 올바르게 기억하면 1000 단위로? 어쨌든 나는 당신이 DataTable로 한계를 우회 할 수 있다는 것을 몰랐으므로 오늘 새로운 것을 배웠습니다 ...
Marko

1
대신 테이블 값 매개 변수를 사용할 수있을 때해야 할 말도 안됩니다. Dapper는 DataTable을 TVP로 전달하는 기능을 완벽하게 지원하므로 임시 테이블 생성 및 삭제는 물론 BulkCopy를 통해 해당 임시 테이블을 채울 수 있습니다. IN 절의 매개 변수 수가 너무 많은 경우에는 TVP 기반 솔루션을 일상적으로 사용합니다.
Mr. T

3
이것은 헬퍼 클래스 또는 확장 메소드를 사용하여 조금 추상화하는 경우에 우스운 양의 작업이 아닙니다.
ErikE

11

또한 쿼리 문자열을 괄호로 묶지 마십시오.

SELECT Name from [USER] WHERE [UserId] in (@ids)

Dapper 1.50.2를 사용하여 괄호를 제거하여 SQL 구문 오류가 발생했습니다.

SELECT Name from [USER] WHERE [UserId] in @ids

7

일반 SQL에서와 같이 WHERE 절 을 추가 할 필요없습니다() . Dapper가 자동으로 처리하기 때문입니다. 여기에 syntax:-

const string SQL = "SELECT IntegerColumn, StringColumn FROM SomeTable WHERE IntegerColumn IN @listOfIntegers";

var conditions = new { listOfIntegers };

var results = connection.Query(SQL, conditions);


3

내 경우에는 이것을 사용했습니다.

var query = "select * from table where Id IN @Ids";
var result = conn.Query<MyEntity>(query, new { Ids = ids });

두 번째 줄에서 내 변수 "ids"는 문자열의 IEnumerable이며 정수 일 수도 있습니다.


List<string>?
Kiquenet

2

내 경험상, 이것을 다루는 가장 친숙한 방법은 문자열을 값 테이블로 변환하는 함수를 갖는 것입니다.

웹에는 많은 스플리터 기능이 있으며 SQL의 풍미에 관계없이 쉽게 찾을 수 있습니다.

그러면 할 수있는 ...

SELECT * FROM table WHERE id IN (SELECT id FROM split(@list_of_ids))

또는

SELECT * FROM table INNER JOIN (SELECT id FROM split(@list_of_ids)) AS list ON list.id = table.id

(또는 유사)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.