当 IN 子句的值列表来自业务逻辑时,使用 Dapper ORM 编写带有 IN 子句的查询的最佳方法是什么?例如,假设我有一个查询:
SELECT *
FROM SomeTable
WHERE id IN (commaSeparatedListOfIDs)
commaSeparatedListOfIDs
是从业务逻辑传入的,它可以是任何类型的IEnumerable(of Integer)
。在这种情况下我将如何构建查询?我是否必须做到目前为止我一直在做的事情,基本上是字符串连接,或者是否有某种我不知道的高级参数映射技术?
Dapper 直接支持这一点。例如...
string sql = "SELECT * FROM SomeTable WHERE id IN @ids"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});
除非您使用 Postgres,在这种情况下请参阅 这个答案
直接来自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
如果您的
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 并使用 TVP 的内部联接执行
UPDATE
。
postgres 示例:
string sql = "SELECT * FROM SomeTable WHERE id = ANY(@ids)"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});
还要确保您的查询字符串没有用括号括起来,如下所示:
SELECT Name from [USER] WHERE [UserId] in (@ids)
我在使用 Dapper 1.50.2 时遇到了这个问题,导致 SQL 语法错误,通过删除括号修复了
SELECT Name from [USER] WHERE [UserId] in @ids
这是使用 ID 列表使用 Dapper 查询大量行的最快方法。
我向你保证,这比任何其他方式都要快,甚至比 TVP 还要快。
它比使用 IN
语法的 Dapper 快
planets,并且比逐行实体框架快 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;
}
}
请注意,您需要了解一些有关批量插入的知识。有一些关于触发触发器(默认为否)、遵守约束、锁定表、允许并发插入等的选项。例如,批量插入仅在事务日志中创建单个条目。这就是他们如此之快的原因之一。
没有必要像在常规 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);
就我而言,我用过这个:
var query = "select * from table where Id IN @Ids";
var result = conn.Query<MyEntity>(query, new { Ids = ids });
第二行中的变量“ids”是一个 IEnumerable 字符串,我猜它们也可以是整数。
根据我的经验,处理此问题最友好的方法是使用一个将字符串转换为值表的函数。
网络上有许多可用的拆分器函数,如果您喜欢 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
(或类似)
SELECT * FROM tbl WHERE col IN @val
我还注意到这种语法不适用于
byte[]
。 Dapper 仅采用最后一个元素,并且参数必须用括号括起来。
但是,当我将类型更改为 int[]
时,一切正常。
对于 PostgreSQL,我发现字符串插值在 .NET 中对我很有用。
示例:
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);