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

Posted

技术标签:

【中文标题】SELECT * FROM X WHERE id IN (...) with Dapper ORM【英文标题】: 【发布时间】:2012-01-13 08:34:42 【问题描述】:

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

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

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

【问题讨论】:

【参考方案1】:

Dapper 直接支持这一点。比如……

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

除非您使用 Postgres,在这种情况下请参阅 this answer

【讨论】:

我认为重要的是要注意,您可以在数组中发送多少项目是有限的。当我传入太多 id 时,我很难意识到这一点。我不记得确切的数字,但根据我的记忆,我认为在 Dapper 停止工作/执行查询之前有 200 个元素。 马克,这很重要。而且,如果您这样做,您可能会考虑寻找另一种查询数据的方法,例如进行连接或反连接,而不是传递 id 列表。 IN 子句不是性能最高的查询,通常可以用 exists 子句代替,这样会更快。 仅供参考 - SQL Server 2008 R2 的 IN 子句限制为 2100 个条目。 而 SQLite 的默认限制是 999 个变量。 注意:在 SQL Server 中,如果您的数组中有多个项目并且您将参数括在括号中,则此操作会失败。删除括号将解决问题。【参考方案2】:

直接来自GitHub project homepage:

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

【讨论】:

【参考方案3】:

如果您的 IN 子句对于 MSSQL 来说太大而无法处理,您可以很容易地将 TableValueParameter 与 Dapper 一起使用。

    在 MSSQL 中创建您的 TVP 类型:

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
    

    创建一个与 TVP 具有相同列的DataTable,并用值填充它

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
    

    修改您的 Dapper 查询以在 TVP 表上执行 INNER JOIN

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
    

    在 Dapper 查询调用中传递 DataTable

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

当您想要对多个列进行大规模更新时,这也非常有效 - 只需构建一个 TVP 并执行 UPDATE 并与 TVP 进行内部连接。

【讨论】:

很好的解决方案,但不适用于 .Net Core,请参阅此问题:***.com/questions/41132350/…。另请参阅此页面:github.com/StackExchange/Dapper/issues/603 您可能还想考虑将MyTVP 上的ProviderId 设为PRIMARY KEY CLUSTERED,因为这只是为我们解决了一个性能问题(我们传递的值不包含重复项)。 @Richardissimo 你能举例说明如何做到这一点吗?我似乎无法使语法正确。 ***.com/questions/4451052/…【参考方案4】:

postgres 示例:

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

【讨论】:

谢谢!我的 postgres db 不喜欢在查询中使用 IN,但是当我使用 ANY【参考方案5】:

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

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

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

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

【讨论】:

【参考方案6】:

这可能是使用 Dapper 使用 ID 列表查询大量行的最快方法。我向你保证,这比你能想到的几乎任何其他方式都快(除了使用另一个答案中给出的 TVP 可能例外,我还没有测试过,但我怀疑可能会慢一些,因为你仍然 必须填充 TVP)。 planets 比使用 IN 语法的 Dapper 快,universes 比实体框架逐行快。它甚至比传递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)、尊重约束、锁定表、允许并发插入等选项。

【讨论】:

是的,我同意您使用 Ids 创建临时表然后在该表上进行内部连接的一般想法。我们已经在内部完成了这项工作,它极大地提高了查询性能。我不确定我是否会使用 DataTable 类,但您的解决方案完全有效。这是一种更快的方法。 批量插入需要DataTable如何向临时表中插入 50,000 个值? 如果我没记错限制,以 1000 个为单位?无论如何,我不知道你可以绕过 DataTable 的限制,所以我今天学到了一些新东西...... 当您可以使用表值参数代替时,这是一个荒谬的工作量。 Dapper 完全支持将 DataTable 作为 TVP 传递,这使您可以省去创建和销毁临时表以及通过 BulkCopy 填充该临时表的麻烦。在 IN 子句的参数数量过多的情况下,我们通常使用基于 TVP 的解决方案。 这不是一个荒谬的工作量,尤其是如果使用辅助类或扩展方法将其抽象一点。【参考方案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);

【讨论】:

【参考方案8】:

就我而言,我使用过这个:

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

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

【讨论】:

List&lt;string&gt; ?【参考方案9】:

根据我的经验,处理这个问题最友好的方法是拥有一个将字符串转换为值表的函数。

网络上有许多可用的拆分器函数,您可以轻松找到一个适合您的 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

(或类似)

【讨论】:

【参考方案10】:
SELECT * FROM tbl WHERE col IN @val

我还注意到此语法不适用于byte[]。 Dapper 只接受最后一个元素,并且参数必须包含在双亲中。 但是,当我将类型更改为 int[] 时,一切正常。

【讨论】:

以上是关于SELECT * FROM X WHERE id IN (...) with Dapper ORM的主要内容,如果未能解决你的问题,请参考以下文章

SQLSTATE [42S22]:未找到列:1054 'where 子句'中的未知列 'id'(SQL:select * from `songs` where `id` = 5 limit 1)

INSERT <table> (x) VALUES (@x) WHERE NOT EXISTS ( SELECT * FROM <table> WHERE x = @x) 会导

哪个更快,select * from table where id=a

laravel SQLSTATE[HY000] [2002] 没有这样的文件或目录 (SQL: select * from `sessions` where `id` =

JDBC Select * from Where x=(?)

PostgreSQL 中的快速随机行:为啥 time (floor(random()*N)) + (select * from a where id = const) 比 select where i