SELECT * FROM X WHERE id IN(…

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

SELECT *
FROM SomeTable
WHERE id IN (commaSeparatedListOfIDs)

commaSeparatedListOfIDs是从业务逻辑传入的,它可以是任何类型的IEnumerable(of Integer)。在这种情况下如何构造查询?我是否需要做我目前所做的基本上是字符串连接还是有一些我不知道的高级参数映射技术?

170874 次浏览

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

在网络上有很多可用的分配器函数,如果你喜欢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

(或相似的)

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

在我的例子中,我使用了这个:

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

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

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

如果你的IN子句太大,MSSQL无法处理,你可以很容易地使用Dapper的TableValueParameter。

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

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
    
  2. 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
    
  3. 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";
    
  4. Pass the DataTable in your Dapper query call

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

This also works fantastically when you want to do a mass update of multiple columns - simply build a TVP and do an UPDATE with an inner join to the TVP.

还要确保你没有像这样用圆括号括起你的查询字符串:

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

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

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

在WHERE子句中添加()没有必要,就像我们在常规SQL中所做的那样。因为达普会自动帮我们做。下面是syntax:-

const string SQL = "SELECT IntegerColumn, StringColumn FROM SomeTable WHERE IntegerColumn IN @listOfIntegers";


var conditions = new { listOfIntegers };
    

var results = connection.Query(SQL, conditions);

postgres的示例:

string sql = "SELECT * FROM SomeTable WHERE id = ANY(@ids)"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});
SELECT * FROM tbl WHERE col IN @val
我还注意到此语法不适用于byte[]。Dapper只接受最后一个元素,参数必须用圆括号括起来。 然而,当我将类型更改为int[]时,一切正常