เลือก * จาก X WHERE ID IN (…) พร้อม Dapper ORM


231

เป็นวิธีที่ดีที่สุดในการเขียนแบบสอบถามด้วยในมาตราการใช้ 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
ฉันคิดว่ามันเป็นสิ่งสำคัญที่จะต้องทราบว่ามีข้อ จำกัด ที่แน่นอนว่าคุณสามารถส่งรายการในอาร์เรย์ได้กี่รายการ ฉันรู้ว่านี่เป็นวิธีที่ยากเมื่อฉันส่งผ่านรหัสมากเกินไป ฉันจำจำนวนที่แน่นอนไม่ได้ แต่จากหน่วยความจำของฉันฉันคิดว่ามันเป็น 200 องค์ประกอบก่อนที่ Dapper จะหยุดทำงาน / เรียกใช้แบบสอบถาม
Marko

8
Marko นั่นสำคัญ และถ้าคุณทำเช่นนั้นคุณอาจลองค้นหาวิธีอื่นในการสืบค้นข้อมูลของคุณเช่นทำการเข้าร่วมหรือต่อต้านการเข้าร่วมแทนที่จะส่งรายการรหัส ส่วนคำสั่ง IN ไม่ใช่แบบสอบถามที่มีประสิทธิภาพสูงสุดและมักจะถูกแทนที่ด้วยส่วนคำสั่งที่มีอยู่ซึ่งจะเร็วกว่า
Don Rolling

24
FYI - SQL Server 2008 R2 มีขีด จำกัด 2100 รายการในที่INข้อ
Jesse

6
และ SQLite นั้นมีค่าเริ่มต้นที่ 999 ตัวแปร
คาเมรอน

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

หากINข้อของคุณใหญ่เกินไปสำหรับ MSSQL ที่จะจัดการคุณสามารถใช้ TableValueParameter กับ Dapper ได้อย่างง่ายดาย

  1. สร้างประเภท TVP ของคุณใน MSSQL:

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
  2. สร้าง a ที่DataTableมีคอลัมน์เดียวกับ TVP และเติมด้วยค่า

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
    
  3. แก้ไขเคียวรี Dapper ของคุณเพื่อทำINNER JOINบนตาราง TVP:

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
    
  4. ผ่าน DataTable ในการเรียกแบบสอบถาม Dapper ของคุณ

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

สิ่งนี้ยังทำงานได้อย่างน่าอัศจรรย์เมื่อคุณต้องการอัปเดตจำนวนมากของคอลัมน์ต่างๆ - เพียงแค่สร้าง TVP และทำการเชื่อมUPDATEต่อภายในกับ TVP


ทางออกที่ดี แต่ไม่ทำงานบนสุทธิหลักดูคำถามนี้: stackoverflow.com/questions/41132350/... ดูหน้านี้: github.com/StackExchange/Dapper/issues/603
pcdev

3
นอกจากนี้คุณยังอาจต้องการที่จะพิจารณาการทำProviderIdบนMyTVPจะเป็นPRIMARY KEY CLUSTEREDเช่นนี้เป็นเพียงแค่การแก้ไขปัญหาประสิทธิภาพการทำงานสำหรับเรา (ค่าที่เราได้ส่งผ่านที่มีอยู่ไม่ซ้ำกัน)
Richardissimo

@Richardissimo คุณสามารถแสดงตัวอย่างของวิธีการทำเช่นนั้นได้หรือไม่? ฉันดูเหมือนจะไม่สามารถแก้ไขไวยากรณ์ได้
Mike Cole


14

นี่อาจเป็นวิธีที่เร็วที่สุดในการสอบถามแถวจำนวนมากด้วย Dapper โดยใช้รายการ ID ฉันสัญญากับคุณว่าจะเร็วกว่าวิธีอื่น ๆ ที่คุณคิด (ยกเว้นการใช้ TVP ตามที่ให้ไว้ในคำตอบอื่นและฉันยังไม่ได้ทดสอบ แต่ฉันสงสัยว่าอาจช้ากว่าเพราะคุณยังต้องใส่ข้อมูล TVP) มันเป็น ดาวเคราะห์ที่เร็วกว่า Dapper โดยใช้INไวยากรณ์และจักรวาลเร็วกว่า Entity Framework ทีละแถว และมันก็ยิ่งเร็วกว่าการผ่านรายการVALUESหรือUNION ALL SELECTรายการ สามารถขยายได้อย่างง่ายดายเพื่อใช้คีย์แบบหลายคอลัมน์เพียงเพิ่มคอลัมน์พิเศษลงในDataTableตาราง temp และเงื่อนไขการเข้าร่วม

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

ระวังว่าคุณต้องเรียนรู้เกี่ยวกับการแทรกจำนวนมากเล็กน้อย มีตัวเลือกเกี่ยวกับทริกเกอร์การยิง (ค่าเริ่มต้นคือไม่มี), เคารพข้อ จำกัด , ล็อคตาราง, อนุญาตการแทรกพร้อมกัน, และอื่น ๆ


ใช่ฉันเห็นด้วยกับแนวคิดทั่วไปของคุณในการสร้างตารางชั่วคราวด้วยรหัสและจากนั้นเข้าร่วมภายในบนโต๊ะนั้น เราได้ทำสิ่งนี้ภายในแล้วและได้ปรับปรุงประสิทธิภาพการสืบค้นอย่างมาก ฉันไม่แน่ใจว่าฉันจะใช้คลาส DataTable เพื่ออะไร แต่โซลูชันของคุณใช้ได้อย่างสมบูรณ์ นี่เป็นวิธีที่เร็วกว่ามาก
มาร์โค

DataTableเป็นสิ่งจำเป็นสำหรับการแทรกเป็นกลุ่ม วิธีทำคุณแทรกตาราง temp 50,000 ค่า?
ErikE

1
ในหน่วย 1,000 ถ้าฉันจำข้อ จำกัด ได้อย่างถูกต้อง? อย่างไรก็ตามฉันไม่ทราบว่าคุณสามารถข้ามขีด จำกัด ด้วย DataTable ได้ดังนั้นฉันจึงได้เรียนรู้สิ่งใหม่ในวันนี้ ...
Marko

1
นั่นเป็นจำนวนงานที่ไร้สาระที่จะไปเมื่อคุณสามารถใช้พารามิเตอร์ค่าตารางแทน Dapper สนับสนุนการส่งผ่าน DataTable ในรูปแบบ TVP อย่างสมบูรณ์ซึ่งช่วยให้คุณจัดการกับการสร้างและทำลายตารางชั่วคราวรวมถึงการเติมตาราง temp นั้นผ่าน BulkCopy เราใช้โซลูชันที่ใช้ TVP เป็นประจำในกรณีที่จำนวนพารามิเตอร์สำหรับส่วนคำสั่ง IN จะมากเกินไป
Mr. T

3
นี่ไม่ใช่งานที่ไร้สาระโดยเฉพาะอย่างยิ่งหากมีบทคัดย่อย่ออยู่เล็กน้อยด้วยคลาสผู้ช่วยหรือวิธีการขยาย
ErikE

11

นอกจากนี้ตรวจสอบให้แน่ใจว่าคุณไม่ต้องใส่วงเล็บในข้อความค้นหาเช่น:

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

ฉันมีสิ่งนี้ทำให้เกิดข้อผิดพลาดทางไวยากรณ์ของ SQL โดยใช้ Dapper 1.50.2 แก้ไขโดยการลบวงเล็บ

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

7

มันเป็นไม่จำเป็นที่จะเพิ่ม()ในคำสั่ง WHERE ในขณะที่เราทำใน SQL ปกติ เพราะ Dapper ทำอย่างนั้นโดยอัตโนมัติสำหรับเรา นี่คือsyntax: -

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

var conditions = new { listOfIntegers };

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

6

ตัวอย่างสำหรับ postgres:

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

3

ในกรณีของฉันฉันใช้สิ่งนี้:

var query = "select * from table where Id IN @Ids";
var result = conn.Query<MyEntity>(query, new { 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.