使用Dapper ORM从X WHERE ID IN(…)中选择*


231

当IN子句的值列表来自业务逻辑时,使用Dapper ORM用IN子句编写查询的最佳方法是什么?例如,假设我有一个查询:

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

commaSeparatedListOfIDs正在从业务逻辑传递,它可以是任何类型的IEnumerable(of Integer)。在这种情况下,我将如何构造查询?到目前为止,我是否必须做我基本上一直在做的事情,即基本上是字符串连接还是我不知道的某种高级参数映射技术?

Answers:


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时,我意识到了这是很难的方法。我不记得确切的数字,但是从我的记忆中,我认为这是200个元素,Dapper停止工作/执行查询。
Marko 2013年

8
马可,那很重要。而且,如果您这样做,则可以考虑寻找另一种查询数据的方式,例如进行联接或反联接,而不是传递ID列表。IN子句不是性能最高的查询,通常可以用一个存在子句代替,这将更快。
Don Rolling

24
仅供参考-SQL Server 2008 R2在该IN子句中的条目限制为2100 。
杰西

6
SQLite的默认限制为999个变量。
卡梅伦2014年

8
当心:在SQL Server中,如果数组中有多个项目并且将参数括在方括号中,则此操作将失败。卸下支架将解决此问题。
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. 使用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. 在Dapper查询调用中传递DataTable

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

当您要对多个列进行批量更新时,这也非常有用-只需构建一个TVP并UPDATE使用内部连接到TVP。


很好的解决方案,但是在.Net Core上不起作用,请参见以下问题:stackoverflow.com/questions/41132350/…。另请参阅此页面:github.com/StackExchange/Dapper/issues/603
pcdev

3
您可能还需要考虑ProviderId使用MyTVPbe PRIMARY KEY CLUSTERED,因为这为我们解决了性能问题(我们传递的值不包含重复项)。
查迪西莫(Richardissimo)'18年

@Richardissimo您能举个例子吗?我似乎无法正确理解语法。
Mike Cole


14

这可能是使用Dapper使用ID列表查询大量行的最快方法。我向您保证,这比您能想到的几乎任何其他方式都要快(除了使用另一个答案中给出的TVP的可能例外,而且我尚未测试过,但我怀疑这样做可能会较慢,因为您仍然需要填充TVP)。它是 行星比使用小巧玲珑更快IN的语法和宇宙不是按照行实体框架排得更快。而且甚至比传递一个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;
   }
}

请注意,您需要了解一些有关批量插入的知识。有关于触发触发器(默认为no),遵守约束,锁定表,允许并发插入等选项。


是的,我同意您的一般想法,即使用Ids创建一个临时表,然后对该表进行内部联接。我们在内部完成了此操作,并大大提高了查询性能。我不确定我将DataTable类用于任何事情,但是您的解决方案完全有效。这是一种更快的方法。
Marko

DataTable是所必需的大容量插入。如何插入到临时表50,000个值?
ErikE

1
如果我正确记得限制,以1000为单位?无论如何,我不知道您可以绕过DataTable的限制,所以今天我学到了一些新东西……
Marko

1
当您可以改用表值参数时,这是一件荒谬的工作。Dapper完全支持将DataTable作为TVP传递,这使您无需创建和销毁临时表,也可以通过BulkCopy填充该临时表。在IN子句的参数数量过多的情况下,我们会常规使用基于TVP的解决方案。
T先生

3
这不是一件荒谬的工作,特别是如果使用助手类或扩展方法将其抽象一些。
ErikE

11

还要确保不要像这样在查询字符串周围加上括号:

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

我使用Dapper 1.50.2导致了SQL语法错误,已通过删除括号进行了修复

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

第二行中的变量“ ids”是字符串的IEnumerable,我猜它们也可以是整数。


List<string>
Kiquenet

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

(或类似)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.