当IN子句的值列表来自业务逻辑时,使用Dapper ORM编写带有IN子句的查询的最佳方法是什么?例如,假设我有一个查询:
SELECT *
FROM SomeTable
WHERE id IN (commaSeparatedListOfIDs)
commaSeparatedListOfIDs是从业务逻辑传入的,它可以是任何类型的IEnumerable(Integer)。在这种情况下如何构造查询?我是否需要做我目前所做的基本上是字符串连接还是有一些我不知道的高级参数映射技术?
当IN子句的值列表来自业务逻辑时,使用Dapper ORM编写带有IN子句的查询的最佳方法是什么?例如,假设我有一个查询:
SELECT *
FROM SomeTable
WHERE id IN (commaSeparatedListOfIDs)
commaSeparatedListOfIDs是从业务逻辑传入的,它可以是任何类型的IEnumerable(Integer)。在这种情况下如何构造查询?我是否需要做我目前所做的基本上是字符串连接还是有一些我不知道的高级参数映射技术?
当前回答
根据我的经验,处理这个问题最友好的方法是使用一个函数将字符串转换为值表。
在网络上有很多可用的分配器函数,如果你喜欢SQL,你很容易就能找到一个。
然后你可以做…
SELECT * FROM table WHERE id IN (SELECT id FROM split(@list_of_ids))
Or
SELECT * FROM table INNER JOIN (SELECT id FROM split(@list_of_ids)) AS list ON list.id = table.id
(或相似的)
其他回答
根据我的经验,处理这个问题最友好的方法是使用一个函数将字符串转换为值表。
在网络上有很多可用的分配器函数,如果你喜欢SQL,你很容易就能找到一个。
然后你可以做…
SELECT * FROM table WHERE id IN (SELECT id FROM split(@list_of_ids))
Or
SELECT * FROM table INNER JOIN (SELECT id FROM split(@list_of_ids)) AS list ON list.id = table.id
(或相似的)
如果你的IN子句太大,MSSQL无法处理,你可以很容易地使用一个TableValueParameter与Dapper。
Create your TVP type in MSSQL: CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL) Create a DataTable with the same column(s) as the TVP and populate it with values var tvpTable = new DataTable(); tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int))); // fill the data table however you wish Modify your Dapper query to do an INNER JOIN on the TVP table: var query = @"SELECT * FROM Providers P INNER JOIN @tvp t ON p.ProviderId = t.ProviderId"; Pass the DataTable in your Dapper query call sqlConn.Query(query, new {tvp = tvpTable.AsTableValuedParameter("dbo.MyTVP")});
当您想要对多个列进行大规模更新时,这也非常有效——只需构建一个TVP,并使用到TVP的内部连接执行update。
SELECT * FROM tbl WHERE col IN @val
我还注意到这种语法不适用于byte[]。Dapper只接受最后一个元素,参数必须用圆括号括起来。 然而,当我将类型更改为int[]时,一切正常。
在我的例子中,我使用了这个:
var query = "select * from table where Id IN @Ids";
var result = conn.Query<MyEntity>(query, new { Ids = ids });
我的变量“ids”在第二行是一个字符串的IEnumerable,我猜他们也可以是整数。
Here is possibly the fastest way to query a large number of rows with Dapper using a list of IDs. I promise you this is faster than almost any other way you can think of (with the possible exception of using a TVP as given in another answer, and which I haven't tested, but I suspect may be slower because you still have to populate the TVP). It is planets faster than Dapper using IN syntax and universes faster than Entity Framework row by row. And it is even continents faster than passing in a list of VALUES or UNION ALL SELECT items. It can easily be extended to use a multi-column key, just add the extra columns to the DataTable, the temp table, and the join conditions.
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)、尊重约束、锁定表、允许并发插入等选项。