如何检查 LINQ 中设置的服务器端列表中是否缺少任何值? (SQL 给出)
How to check for the absence of any value in a list from a set server-side in LINQ? (SQL given)
给定实体 bar
的 属性 foo
,实体集包含大量值,我们如何检查缺失(来自 bar
) 任意值列表中的 any 值,使用 LINQ-to-Entities?
例如:
bar
包含 ALPHA
和 BETA
.
- 我们有一个包含三个项目的列表:
[ALPHA, BETA, GAMMA]
我们要将其发送到 EF 后端 (DBMS)。我们希望后端回复单个标量结果(1 或 0 / true 或 false)。在这种情况下,它将 return 0,因为至少缺少一个值 (GAMMA
)。
- 如果列表中的所有值都在实体集中,查询将return 1。例如,当列表是:
[ALPHA, BETA]
或者简单地 [ALPHA]
或 [BETA]
.
- 我们绝对不想将整个实体集传输到 EF 客户端。
- 我们绝对不想对列表中的每个值发出多个查询。
示例动态 T-SQL:
select case when not exists (
select * from (
select 'ALPHA' foo
union select 'BETA' foo
union select 'GAMMA' foo) a
left join (select distinct foo from bar) b
on a.foo = b.foo
where b.foo is null)
then 1
else 0
end;
我们如何制定可生成此 SQL 查询(或等效查询)的 LINQ-to-Entity 查询?
注意:我以我能想到的最自然的方式编写了这个 SQL 查询(字面意思是 "check that there is no value in the list for which there is no corresponding value in the bar
table")。很可能有更好的方法来做到这一点。
是这样的吗?
bool result = !context.Bars.Any(b => list.Contains(b.Foo));
编辑:我能看到的唯一选择是使用原始 SQL 查询。这将限制您可以使用的后端 DBMS,因为不同的数据库系统支持不同的语法。
您还需要对 table 和列名进行硬编码 - 如果您重构代码,它们将不会被提取。
假设微软 SQL 服务器,像这样的东西应该工作:
var list = new List<string> { "A", "B", "C" };
// Generate a parameter placeholder for each item in the list:
var listParameters = list.Select((item, index) => "@p" + index);
// Generate a "UNION" query for the parameters:
string listValues = string.Join(" UNION SELECT ", listParameters);
// The final query to execute:
string query = @"SELECT CASE WHEN NOT EXISTS
(
SELECT 1
FROM (SELECT " + listValues + @") As L (foo)
WHERE NOT EXISTS
(
SELECT 1
FROM bar As b
WHERE b.foo = L.foo
)
) THEN CAST(1 As bit) ELSE CAST(0 As bit) END";
bool result = context.Database.SqlQuery<bool>(query, list).First();
您正在寻找 All
方法
这会给你正确的答案,但它会导致对列表中的每个项目进行查询。
var list = new[] { "ALPHA", "BETA", "GAMMA" };
bool result = list.All(f => ctx.Bars.Any(b => b.Foo == f));
但是,如果将其写为子查询,它将生成单个查询。
bool result =
ctx.Bars.Select(x =>
list.All(f => ctx.Bars.Any(b => b.Foo == f)) //Same as above but encapsulated
).FirstOrDefault();
请注意,ctx.Bars.Select(x =>
从未实际使用过,它的唯一目的是封装您的 All
逻辑。
另一种选择是在上下文中使用 SelectMany
,然后仅选择列表。查询语法如下所示:
bool result =
(from b in ctx.Bars
from l in list
select l).All(f => ctx.Bars.Any(b => b.Foo == f));
所以就像上面的解决方案一样,我们只使用 from b in ctx.Bars
来强制查询成为 IQueryable
表达式而不是 IEnumerable
。这将生成 1 个查询,而不是 N
个查询
我借用了 的答案,但也许你可以这样做:
var itemList = new List<string> { ... };
// select just foo
var result = context.Bars.Select(b => b.Foo)
// get the distinct values of foo
.Distinct()
// count how many values of foo are in itemList and check if the counts are equal
// because the query is distinct, the count would match iff all items were matched
.Count(f => itemList.Contains(f)) == itemList.Count;
我链接的答案问题还包括其他可根据您的情况进行调整的选项,例如将 Except
与 Any
结合使用。
给定实体 bar
的 属性 foo
,实体集包含大量值,我们如何检查缺失(来自 bar
) 任意值列表中的 any 值,使用 LINQ-to-Entities?
例如:
bar
包含ALPHA
和BETA
.- 我们有一个包含三个项目的列表:
[ALPHA, BETA, GAMMA]
我们要将其发送到 EF 后端 (DBMS)。我们希望后端回复单个标量结果(1 或 0 / true 或 false)。在这种情况下,它将 return 0,因为至少缺少一个值 (GAMMA
)。 - 如果列表中的所有值都在实体集中,查询将return 1。例如,当列表是:
[ALPHA, BETA]
或者简单地[ALPHA]
或[BETA]
. - 我们绝对不想将整个实体集传输到 EF 客户端。
- 我们绝对不想对列表中的每个值发出多个查询。
示例动态 T-SQL:
select case when not exists (
select * from (
select 'ALPHA' foo
union select 'BETA' foo
union select 'GAMMA' foo) a
left join (select distinct foo from bar) b
on a.foo = b.foo
where b.foo is null)
then 1
else 0
end;
我们如何制定可生成此 SQL 查询(或等效查询)的 LINQ-to-Entity 查询?
注意:我以我能想到的最自然的方式编写了这个 SQL 查询(字面意思是 "check that there is no value in the list for which there is no corresponding value in the bar
table")。很可能有更好的方法来做到这一点。
是这样的吗?
bool result = !context.Bars.Any(b => list.Contains(b.Foo));
编辑:我能看到的唯一选择是使用原始 SQL 查询。这将限制您可以使用的后端 DBMS,因为不同的数据库系统支持不同的语法。
您还需要对 table 和列名进行硬编码 - 如果您重构代码,它们将不会被提取。
假设微软 SQL 服务器,像这样的东西应该工作:
var list = new List<string> { "A", "B", "C" };
// Generate a parameter placeholder for each item in the list:
var listParameters = list.Select((item, index) => "@p" + index);
// Generate a "UNION" query for the parameters:
string listValues = string.Join(" UNION SELECT ", listParameters);
// The final query to execute:
string query = @"SELECT CASE WHEN NOT EXISTS
(
SELECT 1
FROM (SELECT " + listValues + @") As L (foo)
WHERE NOT EXISTS
(
SELECT 1
FROM bar As b
WHERE b.foo = L.foo
)
) THEN CAST(1 As bit) ELSE CAST(0 As bit) END";
bool result = context.Database.SqlQuery<bool>(query, list).First();
您正在寻找 All
方法
这会给你正确的答案,但它会导致对列表中的每个项目进行查询。
var list = new[] { "ALPHA", "BETA", "GAMMA" };
bool result = list.All(f => ctx.Bars.Any(b => b.Foo == f));
但是,如果将其写为子查询,它将生成单个查询。
bool result =
ctx.Bars.Select(x =>
list.All(f => ctx.Bars.Any(b => b.Foo == f)) //Same as above but encapsulated
).FirstOrDefault();
请注意,ctx.Bars.Select(x =>
从未实际使用过,它的唯一目的是封装您的 All
逻辑。
另一种选择是在上下文中使用 SelectMany
,然后仅选择列表。查询语法如下所示:
bool result =
(from b in ctx.Bars
from l in list
select l).All(f => ctx.Bars.Any(b => b.Foo == f));
所以就像上面的解决方案一样,我们只使用 from b in ctx.Bars
来强制查询成为 IQueryable
表达式而不是 IEnumerable
。这将生成 1 个查询,而不是 N
个查询
我借用了
var itemList = new List<string> { ... };
// select just foo
var result = context.Bars.Select(b => b.Foo)
// get the distinct values of foo
.Distinct()
// count how many values of foo are in itemList and check if the counts are equal
// because the query is distinct, the count would match iff all items were matched
.Count(f => itemList.Contains(f)) == itemList.Count;
我链接的答案问题还包括其他可根据您的情况进行调整的选项,例如将 Except
与 Any
结合使用。