PILIH * DARI X DI MANA id IN (...) dengan Dapper ORM

231

Apa cara terbaik untuk menulis kueri dengan klausa IN menggunakan Dapper ORM ketika daftar nilai untuk klausa IN berasal dari logika bisnis? Misalnya katakanlah saya punya pertanyaan:

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

Itu commaSeparatedListOfIDssedang diteruskan dari logika bisnis dan itu bisa berupa apa saja IEnumerable(of Integer). Bagaimana cara membuat kueri dalam kasus ini? Apakah saya harus melakukan apa yang telah saya lakukan sejauh ini yang pada dasarnya adalah penggabungan string atau apakah ada semacam teknik pemetaan parameter lanjutan yang tidak saya sadari?

Marko
sumber

Jawaban:

366

Dapper mendukung ini secara langsung. Sebagai contoh...

string sql = "SELECT * FROM SomeTable WHERE id IN @ids"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});
LukeH
sumber
47
Saya pikir penting untuk dicatat bahwa ada batas hingga berapa banyak item yang dapat Anda kirim dalam array Anda. Saya menyadari ini dengan cara yang sulit ketika saya melewati id terlalu banyak. Saya tidak ingat angka pastinya, tetapi dari ingatan saya, saya pikir itu 200 elemen sebelum Dapper berhenti bekerja / menjalankan kueri.
Marko
8
Marko, itu SANGAT penting. Dan, jika Anda melakukannya dengan cara itu, Anda mungkin mempertimbangkan untuk mencari cara lain untuk menanyakan data Anda, seperti melakukan join atau anti-join daripada melewati daftar id. Klausa IN bukan permintaan yang paling berkinerja tinggi dan seringkali dapat diganti dengan klausa yang ada, yang akan lebih cepat.
Don Rolling
24
FYI - SQL Server 2008 R2 memiliki batas 2100 entri pada INklausa.
Jesse
6
Dan SQLite memiliki batas default 999 variabel.
Cameron
8
Hati-hati: di SQL Server ini gagal jika Anda memiliki beberapa item dalam array Anda dan Anda membungkus parameter dalam tanda kurung. Menghapus braket akan memperbaiki masalah.
ajbeaven
66

Langsung dari beranda proyek GitHub :

Dapper memungkinkan Anda untuk lulus dalam IEnumerable dan secara otomatis akan membuat parameter permintaan Anda.

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

Akan diterjemahkan ke:

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
Faktor Mystic
sumber
43

Jika INklausa Anda terlalu besar untuk ditangani MSSQL, Anda bisa menggunakan TableValueParameter dengan Dapper dengan mudah.

  1. Buat tipe TVP Anda di MSSQL:

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
  2. Buat DataTabledengan kolom yang sama dengan TVP dan isi dengan nilai

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
  3. Ubah kueri Dapper Anda untuk melakukan hal INNER JOINdi tabel TVP:

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
  4. Lulus DataTable di panggilan kueri Dapper Anda

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

Ini juga berfungsi secara fantastis ketika Anda ingin melakukan pembaruan massal dari banyak kolom - cukup buat TVP dan lakukan UPDATEdengan gabungan bagian dalam ke TVP.

Tuan T
sumber
Solusi hebat, namun tidak berfungsi di .Net Core, lihat pertanyaan ini: stackoverflow.com/questions/41132350/… . Lihat juga halaman ini: github.com/StackExchange/Dapper/issues/603
pcdev
3
Anda mungkin juga ingin mempertimbangkan ProviderIduntuk MyTVPmenjadi PRIMARY KEY CLUSTERED, karena ini baru saja menyelesaikan masalah kinerja bagi kami (nilai yang kami berikan tidak mengandung duplikat).
Richardissimo
@Richardissimo Bisakah Anda menunjukkan contoh bagaimana melakukan itu? Sepertinya saya tidak bisa mendapatkan sintaks yang benar.
Mike Cole
14

Berikut ini mungkin cara tercepat untuk meminta sejumlah besar baris dengan Dapper menggunakan daftar ID. Saya berjanji kepada Anda ini lebih cepat daripada hampir semua cara lain yang dapat Anda pikirkan (dengan kemungkinan pengecualian menggunakan TVP seperti yang diberikan dalam jawaban lain, dan yang belum saya uji, tapi saya curiga mungkin lebih lambat karena Anda masih harus mengisi TVP). Ini adalah planet yang lebih cepat dari Dapper menggunakan INsintaks dan semesta lebih cepat dari Entity Framework baris demi baris. Dan itu bahkan benua lebih cepat daripada melewati daftar VALUESatau UNION ALL SELECTitem. Dapat dengan mudah diperluas untuk menggunakan kunci multi-kolom, cukup tambahkan kolom ekstra ke DataTable, tabel temp, dan kondisi gabungan.

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

Perlu diketahui bahwa Anda perlu belajar sedikit tentang Sisipan Massal. Ada beberapa opsi tentang pemicu pemicu (defaultnya adalah tidak), menghormati batasan, mengunci tabel, memungkinkan penyisipan bersamaan, dan sebagainya.

ErikE
sumber
Ya saya setuju dengan ide umum Anda membuat tabel temp dengan Id dan kemudian bergabung di dalam tabel itu. Kami telah melakukan ini secara internal dan secara drastis meningkatkan kinerja kueri. Saya tidak yakin saya akan menggunakan kelas DataTable untuk apa pun tetapi solusi Anda benar-benar valid. Ini cara yang jauh lebih cepat.
Marko
The DataTablediperlukan untuk memasukkan massal. Bagaimana Anda menyisipkan tabel temporer 50.000 nilai?
ErikE
1
Dalam potongan 1000 jika saya ingat batasnya dengan benar? Pokoknya saya tidak tahu Anda dapat melewati batas dengan DataTable jadi saya belajar sesuatu yang baru hari ini ...
Marko
1
Itu jumlah yang konyol untuk pergi ketika Anda bisa menggunakan Parameter Nilai Tabel sebagai gantinya. Dapper dengan bersih mendukung lewat DataTable sebagai TVP, yang memungkinkan Anda membuang pembuatan dan penghancuran tabel temp serta mengisi tabel temp tersebut melalui BulkCopy. Kami menggunakan solusi berbasis TVP secara rutin dalam kasus di mana jumlah parameter untuk klausa IN akan terlalu banyak.
Tn.
3
Ini bukan jumlah pekerjaan yang konyol, terutama jika kita abstrak sedikit dengan kelas pembantu atau metode ekstensi.
ErikE
11

Pastikan juga Anda tidak membungkus tanda kurung di sekitar string kueri Anda seperti:

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

Saya punya ini menyebabkan kesalahan Sintaks SQL menggunakan Dapper 1.50.2, diperbaiki dengan menghapus tanda kurung

SELECT Name from [USER] WHERE [UserId] in @ids
Brian Ogden
sumber
7

Tidak perlu menambahkan ()klausa WHERE seperti yang kita lakukan dalam SQL biasa. Karena Dapper melakukan itu secara otomatis untuk kita. Ini dia syntax: -

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

var conditions = new { listOfIntegers };

var results = connection.Query(SQL, conditions);
Coder Absolute
sumber
6

Contoh untuk postgres:

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

Dalam kasus saya, saya telah menggunakan ini:

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

variabel saya "id" di baris kedua adalah IEnumerable dari string, juga mereka bisa bilangan bulat kurasa.

Cesar
sumber
List<string>?
Kiquenet
2

Dalam pengalaman saya, cara paling ramah untuk berurusan dengan ini adalah memiliki fungsi yang mengubah string menjadi tabel nilai.

Ada banyak fungsi splitter yang tersedia di web, Anda akan dengan mudah menemukannya untuk apa pun jika Anda menyukai SQL.

Anda kemudian dapat melakukan ...

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

Atau

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

(Atau serupa)

MatBailie
sumber