CHỌN * TỪ X WHERE id IN (Bắt) với Dapper ORM


231

Cách tốt nhất để viết một truy vấn với mệnh đề IN bằng cách sử dụng Dapper ORM khi danh sách các giá trị cho mệnh đề IN xuất phát từ logic nghiệp vụ? Ví dụ: giả sử tôi có một truy vấn:

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

Điều commaSeparatedListOfIDsnày đang được thông qua từ logic kinh doanh và nó có thể là bất kỳ loại nào IEnumerable(of Integer). Làm thế nào tôi có thể xây dựng một truy vấn trong trường hợp này? Tôi có phải làm những gì tôi đã làm cho đến nay về cơ bản là nối chuỗi hay có một số loại kỹ thuật ánh xạ tham số nâng cao mà tôi không biết?

Câu trả lời:


366

Dapper hỗ trợ điều này trực tiếp. Ví dụ...

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

47
Tôi nghĩ điều quan trọng cần lưu ý là có giới hạn hữu hạn đối với số lượng mục bạn có thể gửi trong mảng của mình. Tôi nhận ra điều này một cách khó khăn khi tôi vượt qua quá nhiều id. Tôi không nhớ số chính xác nhưng từ bộ nhớ của tôi, tôi nghĩ đó là 200 yếu tố trước khi Dapper ngừng hoạt động / thực hiện truy vấn.
Marko

8
Marko, đó là quan trọng. Và, nếu bạn đang làm theo cách đó, bạn có thể cân nhắc tìm một cách khác để truy vấn dữ liệu của mình, chẳng hạn như thực hiện tham gia hoặc chống tham gia thay vì chuyển danh sách id. Mệnh đề IN không phải là truy vấn có hiệu suất cao nhất và thường có thể được thay thế bằng mệnh đề tồn tại, sẽ nhanh hơn.
Don lăn

24
FYI - SQL Server 2008 R2 có giới hạn 2100 mục trên INmệnh đề.
Jesse

6
Và SQLite có giới hạn mặc định là 999 biến.
Cameron

8
Chú ý: trong SQL Server, điều này không thành công nếu bạn có nhiều mục trong mảng và bạn bọc tham số trong ngoặc. Xóa dấu ngoặc sẽ khắc phục vấn đề.
ajbeaven 30/03/2015

66

Trực tiếp từ trang chủ của dự án GitHub :

Dapper cho phép bạn vượt qua trong IEnumerable và sẽ tự động tham số hóa truy vấn của bạn.

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

Sẽ được dịch sang:

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

Nếu INmệnh đề của bạn quá lớn để MSSQL xử lý, bạn có thể sử dụng TableValueParameter với Dapper khá dễ dàng.

  1. Tạo loại TVP của bạn trong MSSQL:

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
  2. Tạo một DataTablecột có cùng cột với TVP và điền vào đó các giá trị

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
    
  3. Sửa đổi truy vấn Dapper của bạn để thực hiện INNER JOINtrên bảng TVP:

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
    
  4. Vượt qua DataTable trong cuộc gọi truy vấn Dapper của bạn

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

Điều này cũng hoạt động tuyệt vời khi bạn muốn thực hiện cập nhật hàng loạt nhiều cột - chỉ cần xây dựng TVP và thực hiện UPDATEvới một kết nối bên trong với TVP.


Tuy nhiên, giải pháp tuyệt vời không hoạt động trên .Net Core, hãy xem câu hỏi này: stackoverflow.com/questions/41132350/ . Cũng xem trang này: github.com/StackExchange/Dapper/issues/603
pcdev

3
Bạn cũng có thể muốn xem xét việc ProviderIdtrên MyTVPđược PRIMARY KEY CLUSTERED, vì điều này chỉ giải quyết một vấn đề hiệu suất đối với chúng tôi (những giá trị chúng ta đã đi qua không chứa bản sao).
Richardissimo

@Richardissimo Bạn có thể chỉ ra một ví dụ về cách làm điều đó không? Tôi dường như không thể có được cú pháp chính xác.
Mike Cole


14

Đây có thể là cách nhanh nhất để truy vấn một số lượng lớn các hàng bằng Dapper bằng danh sách ID. Tôi hứa với bạn điều này nhanh hơn hầu hết mọi cách bạn có thể nghĩ đến (ngoại trừ có thể sử dụng TVP như được đưa ra trong một câu trả lời khác, và tôi đã thử nghiệm, nhưng tôi nghi ngờ có thể chậm hơn vì bạn vẫn phải cư trú TVP). Nó là các hành tinh nhanh hơn Dapper bằng cách sử dụng INcú pháp và vũ trụ nhanh hơn Entity Framework theo từng hàng. Và nó thậm chí còn nhanh hơn các danh sách VALUEShoặc UNION ALL SELECTvật phẩm. Nó có thể dễ dàng được mở rộng để sử dụng khóa nhiều cột, chỉ cần thêm các cột bổ sung vào DataTable, bảng tạm thời và các điều kiện nối.

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

Hãy lưu ý rằng bạn cần tìm hiểu một chút về Chèn hàng loạt. Có các tùy chọn về kích hoạt bắn (mặc định là không), tôn trọng các ràng buộc, khóa bảng, cho phép chèn đồng thời, v.v.


Có, tôi đồng ý với ý tưởng chung của bạn về việc tạo bảng tạm thời với Id và sau đó kết nối bên trong trên bảng đó. Chúng tôi đã thực hiện điều này trong nội bộ và nó đã cải thiện đáng kể hiệu năng truy vấn. Tôi không chắc chắn tôi sẽ sử dụng lớp DataTable cho bất cứ điều gì nhưng giải pháp của bạn hoàn toàn hợp lệ. Đây là một cách nhanh hơn nhiều.
Marko

Các DataTableyêu cầu cho chèn số lượng lớn. Làm thế nào để bạn chèn vào bảng tạm thời 50.000 giá trị?
ErikE

1
Trong khối 1000 nếu tôi nhớ chính xác giới hạn? Dù sao, tôi không biết bạn có thể vượt qua giới hạn với DataTable vì vậy tôi đã học được một điều mới ngày hôm nay ...
Marko

1
Đó là một lượng công việc vô lý phải làm khi bạn có thể sử dụng Tham số Giá trị Bảng thay thế. Dapper hoàn toàn hỗ trợ chuyển DataTable dưới dạng TVP, cho phép bạn phân phối với việc tạo và hủy bảng tạm thời cũng như điền vào bảng tạm thời đó qua Số lượng lớn. Chúng tôi sử dụng giải pháp dựa trên TVP thường xuyên trong trường hợp số lượng tham số cho mệnh đề IN sẽ quá nhiều.
Ông T

3
Đây không phải là một khối lượng công việc lố bịch, đặc biệt nếu một người trừu tượng hóa nó một chút với một lớp trợ giúp hoặc phương thức mở rộng.
ErikE

11

Ngoài ra, hãy đảm bảo bạn không bọc các dấu ngoặc đơn xung quanh chuỗi truy vấn của mình như vậy:

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

Tôi đã gây ra lỗi SQL Cú pháp bằng Dapper 1.50.2, được sửa bằng cách xóa dấu ngoặc đơn

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

7

Không cần thiết phải thêm ()vào mệnh đề WHERE như chúng ta làm trong SQL thông thường. Bởi vì Dapper làm điều đó tự động cho chúng tôi. Đây là syntax: -

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

var conditions = new { listOfIntegers };

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

6

Ví dụ cho postgres:

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

3

Trong trường hợp của tôi, tôi đã sử dụng điều này:

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

biến "id" của tôi trong dòng thứ hai là vô số chuỗi, tôi cũng có thể là số nguyên.


List<string>?
Kiquenet

2

Theo kinh nghiệm của tôi, cách xử lý thân thiện nhất với điều này là có một hàm chuyển đổi một chuỗi thành một bảng các giá trị.

Có rất nhiều hàm chia có sẵn trên web, bạn sẽ dễ dàng tìm thấy một hàm cho bất cứ thứ gì nếu hương vị SQL của bạn.

Sau đó bạn có thể làm ...

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

Hoặc là

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

(Hoặc tương tự)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.