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 比实体框架逐行快。它甚至比传递VALUES
或UNION 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<string>
?【参考方案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