SELECT * FROM X WHERE id IN(…)with Dapper ORM


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
マルコ、それは重要です。また、その方法で行う場合は、IDのリストを渡すのではなく、結合や反結合を行うなど、データをクエリする別の方法を見つけることを検討してください。IN句は、最もパフォーマンスの高いクエリではなく、より高速なexists句に置き換えることができます。
Don Rolling

24
参考までに-SQL Server 2008 R2では、IN句に2100エントリの制限があります。
ジェシー、

6
SQLiteには、デフォルトで999変数の制限があります。
キャメロン

8
注意:SQL Serverでは、配列に複数の項目があり、パラメーターを角かっこで囲む場合、これは失敗します。ブラケットを取り外すと問題が解決します。
ajbeaven 2015年

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が処理するには大きすぎる、あなたはかなり簡単に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. Dapperクエリを変更INNER JOINして、TVPテーブルで次を実行します。

    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)。これは、ある 惑星速くDapperのは、使用するよりもIN構文とユニバースをより速く行により、Entity Frameworkの行よりも。また、リストのVALUESUNION 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;
   }
}

一括挿入について少し学ぶ必要があることに注意してください。トリガーの起動(デフォルトはno)、制約の尊重、テーブルのロック、同時挿入の許可などに関するオプションがあります。


はい、IDを使用して一時テーブルを作成し、そのテーブルに内部結合するという一般的な考えに同意します。これを内部で実行し、クエリのパフォーマンスを大幅に改善しました。DataTableクラスを何かに使用するかどうかはわかりませんが、ソリューションは完全に有効です。これははるかに速い方法です。
マルコ

DataTable一括挿入のために必要とされます。一時テーブルに50,000値をどのよう挿入しますか?
ErikE 2017年

1
1000のチャンクで制限を正しく覚えていれば?とにかく、DataTableを使用して制限を回避できることを知らなかったので、今日新しいことを学びました...
Marko

1
代わりにテーブル値パラメーターを使用できる場合、これはとんでもない作業です。Dapperは、DataTableをTVPとして渡すことを完全にサポートしています。これにより、一時テーブルの作成と破棄を不要にし、BulkCopyを介してその一時テーブルにデータを入力できます。IN句のパラメーターの数が多すぎる場合は、TVPベースのソリューションを日常的に使用します。
T氏

3
これは、特にヘルパークラスまたは拡張メソッドを使用して少し抽象化する場合、ばかげた作業ではありません。
ErikE 2018年

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

2行目の変数 "ids"は、文字列のIEnumerableです。また、それらは私が推測する整数にすることもできます。


List<string>
Kiquenet 2018年

2

私の経験では、これに対処する最もフレンドリーな方法は、文字列を値のテーブルに変換する関数を持つことです。

Webには多くのスプリッター関数があり、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.