323

What is the best way to write a query with IN clause using Dapper ORM when the list of values for the IN clause is coming from business logic? For example let's say I have a query:

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

The commaSeparatedListOfIDs is being passed in from business logic and it can be any type of IEnumerable(of Integer). How would I construct a query in this case? Do I have to do what I've been doing so far which is basically string concatenation or is there some sort of advanced parameter mapping technique that I'm not aware of?

Zsolt Botykai
  • 50,406
  • 14
  • 85
  • 110
Marko
  • 12,543
  • 10
  • 48
  • 58

11 Answers11

513

Dapper supports this directly. For example...

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

unless you are using Postgres, in which case see this answer

Chris F Carroll
  • 11,146
  • 3
  • 53
  • 61
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • 70
    I think it's important to note that there is a finite limit to how many items can you send in your array. I realized this the hard way when I passed in too many ids. I don't remember the exact number but from my memory I think it's 200 elements before Dapper stops working/executing the query. – Marko Mar 05 '13 at 20:43
  • 15
    Marko, that IS important. And, if you are doing it that way, you might consider finding another way of querying your data, such as doing a join or an anti-join rather than passing a list of ids. The IN clause is not the most highly performing query and can often be replaced by an exists clause, which will be faster. – Don Rolling Sep 18 '13 at 15:25
  • 37
    FYI - SQL Server 2008 R2 has a limit of 2100 entries on the `IN` clause. – Jesse May 20 '14 at 18:09
  • 11
    And SQLite has a default limit of 999 variables. – Cameron Aug 26 '14 at 15:24
  • 1
    This is awesome, however I am wondering what sql type should I use for "@ids", if I were to write this sql in a stored procedure...any thoughts? – user153410 Sep 14 '14 at 18:47
  • 2
    Does anyone have a solution to replace this with a TVP, to overcome the limit, in sql server 2008? – cs0815 Oct 20 '14 at 15:39
  • 22
    Beware: in SQL Server this fails if you have multiple items in your array and you wrap the parameter in brackets. Removing the brackets will fix the issue. – ajbeaven Mar 30 '15 at 02:35
  • @csetzkorn It doesn't seem to me that there is much use case for an IN statement outside of adding or eliminating a few specific things from a data set. If many things need to be added or excluded, then find a way to turn that into a query and use it in an EXISTS statement. – Don Rolling Aug 12 '16 at 18:13
  • 1
    Does anyone knows how to implement this in Stored Procedure? Is it possible? – Sulaiman Adeeyo Apr 18 '17 at 18:41
  • I would just like to add that this approach with with any IEnumerable type. You do not explicitly need an array. – Newteq Developer Aug 05 '19 at 10:50
  • 2
    Doesn't work for me. I'm using postgres. The paramenter is replaced with "$1", which is passed to server and I get a syntax error. Any ideas how to make it work? – jira Sep 04 '19 at 10:32
  • 5
    @jira I answered here https://stackoverflow.com/questions/8388093/select-from-x-where-id-in-with-dapper-orm/58448315#58448315 – SanŚ́́́́Ý́́́́Ś́́́́ Oct 18 '19 at 09:51
  • Not sure why but when I run this it fails with the following exception when i pass an enumerable of GUID: System.InvalidOperationException: An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context – Essej Nov 06 '20 at 13:41
81

Directly from the GitHub project homepage:

Dapper allow you to pass in IEnumerable and will automatically parameterize your query.

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

Will be translated to:

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
juharr
  • 31,741
  • 4
  • 58
  • 93
Factor Mystic
  • 26,279
  • 16
  • 79
  • 95
57

If your IN clause is too big for MSSQL to handle, you can use a TableValueParameter with Dapper pretty easily.

  1. Create your TVP type in MSSQL:

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
    
  2. Create a DataTable with the same column(s) as the TVP and populate it with values

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
    
  3. Modify your Dapper query to do an INNER JOIN on the TVP table:

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
    
  4. Pass the DataTable in your Dapper query call

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

This also works fantastically when you want to do a mass update of multiple columns - simply build a TVP and do an UPDATE with an inner join to the TVP.

Might
  • 305
  • 3
  • 12
Mr. T
  • 3,892
  • 3
  • 29
  • 48
  • 2
    Great solution, however doesn't work on .Net Core, see this question: https://stackoverflow.com/questions/41132350/is-there-a-way-to-pass-a-tvp-to-dapper-on-net-core-right-now. Also see this page: https://github.com/StackExchange/Dapper/issues/603 – pcdev Oct 18 '17 at 06:51
  • 3
    You may also want to consider making `ProviderId` on `MyTVP` be `PRIMARY KEY CLUSTERED`, as this just solved a performance issue for us (the values we were passing contained no duplicates). – Richardissimo Jul 18 '18 at 10:33
  • @Richardissimo Can you show an example of how to do that? I can't seem to get the syntax correct. – Mike Cole Nov 20 '18 at 02:22
  • https://stackoverflow.com/questions/4451052/how-to-add-an-index-or-primary-key-to-a-user-defined-table-type-in-sql-server – Richardissimo Nov 20 '18 at 06:24
  • Just note that if you STILL have worse performance than desired with this technique, you should consider the temp-table-bulk-insert technique in my answer on this question. Bulk inserts are special. We're talking reasonable performance for 100,000 values. – ErikE Jun 16 '23 at 18:44
52

Example for postgres:

string sql = "SELECT * FROM SomeTable WHERE id = ANY(@ids)"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});
  • 6
    Thank you! My postgres db didn't like it with `IN` in the query, but it worked perfectly when I used `ANY` – melicent May 10 '21 at 15:44
24

Also make sure you do not wrap parentheses around your query string like so:

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

I had this cause a SQL Syntax error using Dapper 1.50.2, fixed by removing parentheses

SELECT Name from [USER] WHERE [UserId] in @ids
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176
18

Here is possibly the fastest way to query a large number of rows with Dapper using a list of IDs. I promise you this is faster than almost any other way you can think of (with the possible exception of using a TVP as given in another answer, and which I haven't tested, but I suspect may be slower because you still have to populate the TVP). It is planets faster than Dapper using IN syntax and universes faster than Entity Framework row by row. And it is even continents faster than passing in a list of VALUES or UNION ALL SELECT items. It can easily be extended to use a multi-column key, just add the extra columns to the DataTable, the temp table, and the join conditions.

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

Be aware that you need to learn a little bit about Bulk Inserts. There are options about firing triggers (the default is no), respecting constraints, locking the table, allowing concurrent inserts, and so on.

ErikE
  • 48,881
  • 23
  • 151
  • 196
  • Yes I agree with your general idea of creating a temp table with Ids and then inner joining on that table. We have done this internally and it drastically improved the query performance. I'm not sure I would use the DataTable class for anything but your solution totally is valid. This is a much faster way. – Marko Jan 26 '17 at 19:16
  • The `DataTable` is required for the bulk insert. How do *you* insert to the temp table 50,000 values? – ErikE Jan 26 '17 at 20:23
  • 1
    In chunks of 1000 if I remember the limit correctly? Anyway I did not know you can bypass the limit with DataTable so I learned something new today... – Marko Jan 27 '17 at 02:39
  • The `DataTable` is just the prerequisite for the bulk insert. Bulk inserts are special. They are done in a special mode that makes them much faster than a normal insert. – ErikE Jan 27 '17 at 03:10
  • 2
    That's a ridiculous amount of work to go to when you could use a Table Value Parameter instead. Dapper cleanly supports passing a DataTable as a TVP, which lets you dispense with the creation and destruction of a temp table as well as populating that temp table via BulkCopy. We use the TVP based solution routinely in cases where the number of parameters for the IN clause would be too many. – Mr. T Mar 28 '17 at 20:46
  • @Mr.T Does Dapper populate the TVP using BulkCopy? Can you provide a link to example code using a TVP with Dapper in the way you suggest? – ErikE Mar 28 '17 at 21:02
  • @ErikE - I posted an answer with a code snippet that should get you started. – Mr. T Mar 30 '17 at 21:23
  • 3
    This isn't a ridiculous amount of work, especially if one abstracts it away a little bit with a helper class or extension method. – ErikE Jan 03 '18 at 19:55
15

It is not necessary to add () in the WHERE clause as we do in a regular SQL. Because Dapper does that automatically for us. Here is the syntax:-

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

var conditions = new { listOfIntegers };
    
var results = connection.Query(SQL, conditions);
Palle Due
  • 5,929
  • 4
  • 17
  • 32
Coder Absolute
  • 5,417
  • 5
  • 26
  • 41
4

In my case I've used this:

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

my variable "ids" in the second line is an IEnumerable of strings, also they can be integers I guess.

Cesar
  • 189
  • 8
1

In my experience, the most friendly way of dealing with this is to have a function that converts a string into a table of values.

There are many splitter functions available on the web, you'll easily find one for whatever if your flavour of SQL.

You can then do...

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

Or

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

(Or similar)

MatBailie
  • 83,401
  • 18
  • 103
  • 137
0
SELECT * FROM tbl WHERE col IN @val

I have also noticed that this syntax does not work with byte[]. Dapper takes only the last element and the parameter must be wrapped in parenteses. However, when I change the type to int[] everything works.

mihails.kuzmins
  • 1,140
  • 1
  • 11
  • 19
-2

For PostgreSQL, I found that string interpolation was useful for me in .NET.

Example:

var ids = new int[] { 1, 2, 3 };
var query = "SELECT name FROM table WHERE id IN ({string.Join(",", ids)})";
using var connection = _dapperContext.CreateConnection();
var results = await connection.QueryAsync<ResultModel>(query);
A. Faisal
  • 1
  • 2