【问题标题】:SELECT * FROM X WHERE id IN (...) with Dapper ORMSELECT * 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)。在这种情况下,我将如何构建查询?我必须做到目前为止我一直在做的事情,基本上是字符串连接,还是有某种我不知道的高级参数映射技术?

【问题讨论】:

    标签: .net sql dapper


    【解决方案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 一起使用。

      1. 在 MSSQL 中创建您的 TVP 类型:

        CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
        
      2. 创建一个与 TVP 具有相同列的DataTable,并用值填充它

        var tvpTable = new DataTable();
        tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
        // fill the data table however you wish
        
      3. 修改您的 Dapper 查询以在 TVP 表上执行 INNER JOIN

        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 进行内部连接。

      【讨论】:

      【解决方案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[] 时,一切正常。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-09-27
              • 1970-01-01
              • 2011-04-06
              • 1970-01-01
              • 2010-11-03
              • 1970-01-01
              • 2020-04-18
              • 2011-01-27
              相关资源
              最近更新 更多