在 `LINQ to Entities` 中使用 `System.String Concat` 生成 `CAST` 而不是 `CONCAT`
Using `System.String Concat` in `LINQ to Entities` generates `CAST`s instead of `CONCAT`
正如我从 docs 中了解到的那样,当使用 string.Concat
查询技术(例如 LINQ to Entities)时,应该将此规范函数转换为正确的对应存储函数正在使用的提供商,意味着 Concat
用于 MsSQL
的 T-SQL
。但是当我运行下面的测试代码:
var items = (from item in testDB.Items
where list.Contains(string.Concat(item.Id,":"))
select new
{
Hit = string.Concat(item.Id,":"),
Item = item
}).ToList();
正在制作以下SQL:
SELECT
[Extent1].[Id] AS [Id],
CAST( [Extent1].[Id] AS nvarchar(max)) + N':' AS [C1],
FROM [testDB].[Items] AS [Extent1]
WHERE (CAST([Extent1].[Id] AS nvarchar(max)) + N':' -- N.B.
IN (N'1:', N'2:', N'3:')) AND (CAST([Extent1].[Id] AS nvarchar(max)) + N':' IS NOT NULL)
N.B.: +
(加号运算符)使用 CAST
而不是 Concat
。
显然我做错了什么,但是呢?问题是 CAST
到 NVARCHAR(MAX)
需要花费大量时间,尤其是在连接多个字段时。
好像做不到,因为Concat
使用sql_variant
类型,SQLProviderManifest
没有定义,不支持重载所以只能映射一个函数签名(架构中的名称应该是唯一的),所以很明显他们只是在需要时通过使用加号运算符与强制转换来连接短路,即使我在代码优先中映射 Concat
时,生成的 SQL
仍在使用它,所以它变得很明显。我认为唯一的方法是利用 DbSet.SqlQuery
.
可以使用 item.Id+":"
我经常用这个
我认为您可以从 SQL 中删除所有的串联。看起来你有一个字符串列表,其中包含数字后跟一个冒号。为什么不去掉数字中的冒号并直接进行数字比较?
var list2 = list.Select(i => int.Parse(i.Replace(':','')));
var items = (from item in testDB.Items
where list2.Contains(item.Id,":"))
.AsEnumerable() // switch from DB query to memory query
.Select(item => new
{
Hit = string.Concat(item.Id,":"),
Item = item
}).ToList();
所以为了让事情正常进行,我们不会使用 LINQ
,而是 DbSet.SqlQuery
:
var contains = list.Aggregate(new System.Text.StringBuilder(),
(sb, s) => sb.Append($"N'{s}', "),
sb => (sb.Length > 0)
? sb.ToString(0, sb.Length - 2)
: null);
if (contains == null)
{
//ToDo: list is empty, we've got a problem
}
else
{
var query = testDB.Items.SqlQuery($@"SELECT [E1].[Id],..
FROM [testDB].[Items] AS [E1]
WHERE CONCAT_WS(':', [E1].[Id],..) IN ({contains })");
}
当然应该用 SqlParameter
来完成,但这是解决方法的一般思路。
正如我从 docs 中了解到的那样,当使用 string.Concat
查询技术(例如 LINQ to Entities)时,应该将此规范函数转换为正确的对应存储函数正在使用的提供商,意味着 Concat
用于 MsSQL
的 T-SQL
。但是当我运行下面的测试代码:
var items = (from item in testDB.Items
where list.Contains(string.Concat(item.Id,":"))
select new
{
Hit = string.Concat(item.Id,":"),
Item = item
}).ToList();
正在制作以下SQL:
SELECT
[Extent1].[Id] AS [Id],
CAST( [Extent1].[Id] AS nvarchar(max)) + N':' AS [C1],
FROM [testDB].[Items] AS [Extent1]
WHERE (CAST([Extent1].[Id] AS nvarchar(max)) + N':' -- N.B.
IN (N'1:', N'2:', N'3:')) AND (CAST([Extent1].[Id] AS nvarchar(max)) + N':' IS NOT NULL)
N.B.: +
(加号运算符)使用 CAST
而不是 Concat
。
显然我做错了什么,但是呢?问题是 CAST
到 NVARCHAR(MAX)
需要花费大量时间,尤其是在连接多个字段时。
好像做不到,因为Concat
使用sql_variant
类型,SQLProviderManifest
没有定义,不支持重载所以只能映射一个函数签名(架构中的名称应该是唯一的),所以很明显他们只是在需要时通过使用加号运算符与强制转换来连接短路,即使我在代码优先中映射 Concat
时,生成的 SQL
仍在使用它,所以它变得很明显。我认为唯一的方法是利用 DbSet.SqlQuery
.
可以使用 item.Id+":" 我经常用这个
我认为您可以从 SQL 中删除所有的串联。看起来你有一个字符串列表,其中包含数字后跟一个冒号。为什么不去掉数字中的冒号并直接进行数字比较?
var list2 = list.Select(i => int.Parse(i.Replace(':','')));
var items = (from item in testDB.Items
where list2.Contains(item.Id,":"))
.AsEnumerable() // switch from DB query to memory query
.Select(item => new
{
Hit = string.Concat(item.Id,":"),
Item = item
}).ToList();
所以为了让事情正常进行,我们不会使用 LINQ
,而是 DbSet.SqlQuery
:
var contains = list.Aggregate(new System.Text.StringBuilder(),
(sb, s) => sb.Append($"N'{s}', "),
sb => (sb.Length > 0)
? sb.ToString(0, sb.Length - 2)
: null);
if (contains == null)
{
//ToDo: list is empty, we've got a problem
}
else
{
var query = testDB.Items.SqlQuery($@"SELECT [E1].[Id],..
FROM [testDB].[Items] AS [E1]
WHERE CONCAT_WS(':', [E1].[Id],..) IN ({contains })");
}
当然应该用 SqlParameter
来完成,但这是解决方法的一般思路。