EntityFramework:SQL 上的 Linq:包含或 IndexOf?
EntityFramework: Linq on SQL: Contains or IndexOf?
关于使用 .NET 4.5 (C#) 的 EntityFramework 6,我有一个奇怪的情况。
我在两个不同的地方有(几乎)相同的查询。但是一次它查询数据库,第二次查询内存中的对象。因为我过滤的是子字符串,所以这是一个关键的区别:
数据库结构都是tables Role, right and a crosstable Role_Right
我第一次想找到所有尚未分配给该角色的可用权限加上(这就是它变得复杂的地方)一个手动过滤器来减少结果列表:
Role role = ...;
string filter = ...;
var roleRightNames = role.Right.Select(roleRight => roleRight.RightName);
var filteredRights = context.Right.Where(right => !roleRightNames.Contains(right.RightName));
if (!string.IsNullOrWhiteSpace(filter))
{
filteredRights = filteredRights.Where(e => e.RightName.Contains(filter));
}
var result = filteredRights.ToList();
我无法使用 IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) >= 0)
,因为这无法转换为 SQL。但我对 Contains
没问题,因为它会产生所需的结果(见下文)。
启用 SQL 输出时,我得到:
SELECT [Extent1].[RightName] AS [RightName]
FROM [dbo].[Right] AS [Extent1]
WHERE ( NOT ([Extent1].[RightName] IN ('Right_A1', 'Right_A2', 'Right_B1'))) AND ([Extent1].[RightName] LIKE @p__linq__0 ESCAPE '~'
-- p__linq__0: '%~_a%' (Type = AnsiString, Size = 8000)
这正是我想要的,在过滤器“_a”上进行不区分大小写的搜索以查找例如 'Right_A3'
我第二次要为同一个过滤器过滤现有的关联权限:
Role role = ...;
string filter = ...;
var filteredRights = string.IsNullOrWhiteSpace(filter)
? role.Right
: role.Right.Where(e => e.RightName.IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) >= 0);
var result = filteredRights.ToList();
这次它迫使我使用 IndexOf
因为它使用 string
的 Contains
方法而不是将其转换为 SQL LIKE
string.Contains
区分大小写。
我的问题是我无法 - 通过查看代码 - 预测何时对数据库执行查询以及何时在内存中完成查询,因为我不能在第一个查询中使用 IndexOf
和Contains
第二个对我来说似乎有点不可思议table。如果有一天第二个查询先执行并且数据不在内存中,会发生什么情况?
编辑 2020 年 2 月 10 日
好的,所以我弄清楚了主要区别是什么。 context.Right
属于 DbSet
类型,它是 IQueryable
类型,后续扩展方法 Where
也是如此。但是 userRole.Right
returns 一个 ICollection
是一个 IEnumerable
,随后的 Where
也是如此。有没有办法使实体对象的关系 属性 成为 IQueryable
? AsQueryable
无效。这意味着所有关联的 Right
实体总是在执行内存中 Where
之前从数据库中获取。
我们不是在谈论大量数据,至少现在这种行为是预测的table,但我仍然觉得它很不幸。
My problem is that I cannot - from looking at the code - predict when
a query is executed against the database and when it is done in-memory
and since I cannot use IndexOf in the first query and Contains in the
second this seems to be a bit unpredictable to me.
您可以在两个查询中使用 IndexOf
和 Contains
,只要您不使用具有 StringComparison
的重载。正如@BrettCaswell 所指出的,大小写匹配由 Database/Table/Column 的排序规则确定。
如果查询的根是上下文的 DbSet
并且所有方法调用都可转换为 SQL.
,则该查询将被转换为 SQL
一旦无法转换方法,就会在 SQL 级别执行当前状态请求,并且在 .Net 应用程序的内存中执行查询的其余部分。
另外我认为 p__linq__0
值应该是 '%~_a%'
因为 _
是 LIKE
子句中的特殊字符。
好的,所以我找到了两种不同的解决方案,以在关系包含巨大结果集的情况下始终查询数据库。这两种解决方案都不是直接直观的 - 恕我直言 - 你将需要你以前不需要的 DbContext
变量。
解决方案一是使用 Role
table 作为起点并简单地过滤具有正确 ID 的实体。
注意您不能使用Single
,因为那样您处理的是单个实体对象,您又回到了开始的地方。您需要使用 Where
,然后使用 SelectMany
,即使它违反直觉:
Role role = ...;
string filter = ...;
var filteredRights = context.Role.Where(e => e.RoleId == userRole.RoleId).SelectMany(e => e.Right);
if (!string.IsNullOrWhiteSpace(filter))
{
filteredRights = filteredRights.Where(e => e.RightName.Contains(filter));
}
var rights = filteredRights.ToList();
这导致对数据库的 SQL 查询:
SELECT
[Extent1].[RightName] AS [RightName]
FROM [dbo].[Role_Right] AS [Extent1]
WHERE ([Extent1].[RoleId] = @p__linq__0) AND ([Extent1].[RightName] LIKE @p__linq__1 ESCAPE '~')
-- p__linq__0: '42' (Type = Int32, IsNullable = false)
-- p__linq__1: '%~_a%' (Type = AnsiString, Size = 8000)
我在这里找到的第二个解决方案:
在我的例子中,结果是:
Role role = ...;
string filter = ...;
var filteredRights = context.Entry(userRole).Collection(e => e.Right).Query();
if (!string.IsNullOrWhiteSpace(filter))
{
filteredRights = filteredRights.Where(e => e.RightName.Contains(filter));
}
var rights = filteredRights.ToList();
和SQL
SELECT
[Extent1].[RightName] AS [RightName]
FROM [dbo].[Role_Right] AS [Extent1]
WHERE ([Extent1].[RoleId] = @EntityKeyValue1) AND ([Extent1].[RightName] LIKE @p__linq__0 ESCAPE '~')
-- EntityKeyValue1: '42' (Type = Int32, IsNullable = false)
-- p__linq__0: '%~_a%' (Type = AnsiString, Size = 8000)
关于使用 .NET 4.5 (C#) 的 EntityFramework 6,我有一个奇怪的情况。
我在两个不同的地方有(几乎)相同的查询。但是一次它查询数据库,第二次查询内存中的对象。因为我过滤的是子字符串,所以这是一个关键的区别:
数据库结构都是tables Role, right and a crosstable Role_Right
我第一次想找到所有尚未分配给该角色的可用权限加上(这就是它变得复杂的地方)一个手动过滤器来减少结果列表:
Role role = ...;
string filter = ...;
var roleRightNames = role.Right.Select(roleRight => roleRight.RightName);
var filteredRights = context.Right.Where(right => !roleRightNames.Contains(right.RightName));
if (!string.IsNullOrWhiteSpace(filter))
{
filteredRights = filteredRights.Where(e => e.RightName.Contains(filter));
}
var result = filteredRights.ToList();
我无法使用 IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) >= 0)
,因为这无法转换为 SQL。但我对 Contains
没问题,因为它会产生所需的结果(见下文)。
启用 SQL 输出时,我得到:
SELECT [Extent1].[RightName] AS [RightName]
FROM [dbo].[Right] AS [Extent1]
WHERE ( NOT ([Extent1].[RightName] IN ('Right_A1', 'Right_A2', 'Right_B1'))) AND ([Extent1].[RightName] LIKE @p__linq__0 ESCAPE '~'
-- p__linq__0: '%~_a%' (Type = AnsiString, Size = 8000)
这正是我想要的,在过滤器“_a”上进行不区分大小写的搜索以查找例如 'Right_A3'
我第二次要为同一个过滤器过滤现有的关联权限:
Role role = ...;
string filter = ...;
var filteredRights = string.IsNullOrWhiteSpace(filter)
? role.Right
: role.Right.Where(e => e.RightName.IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) >= 0);
var result = filteredRights.ToList();
这次它迫使我使用 IndexOf
因为它使用 string
的 Contains
方法而不是将其转换为 SQL LIKE
string.Contains
区分大小写。
我的问题是我无法 - 通过查看代码 - 预测何时对数据库执行查询以及何时在内存中完成查询,因为我不能在第一个查询中使用 IndexOf
和Contains
第二个对我来说似乎有点不可思议table。如果有一天第二个查询先执行并且数据不在内存中,会发生什么情况?
编辑 2020 年 2 月 10 日
好的,所以我弄清楚了主要区别是什么。 context.Right
属于 DbSet
类型,它是 IQueryable
类型,后续扩展方法 Where
也是如此。但是 userRole.Right
returns 一个 ICollection
是一个 IEnumerable
,随后的 Where
也是如此。有没有办法使实体对象的关系 属性 成为 IQueryable
? AsQueryable
无效。这意味着所有关联的 Right
实体总是在执行内存中 Where
之前从数据库中获取。
我们不是在谈论大量数据,至少现在这种行为是预测的table,但我仍然觉得它很不幸。
My problem is that I cannot - from looking at the code - predict when a query is executed against the database and when it is done in-memory and since I cannot use IndexOf in the first query and Contains in the second this seems to be a bit unpredictable to me.
您可以在两个查询中使用 IndexOf
和 Contains
,只要您不使用具有 StringComparison
的重载。正如@BrettCaswell 所指出的,大小写匹配由 Database/Table/Column 的排序规则确定。
如果查询的根是上下文的 DbSet
并且所有方法调用都可转换为 SQL.
一旦无法转换方法,就会在 SQL 级别执行当前状态请求,并且在 .Net 应用程序的内存中执行查询的其余部分。
另外我认为 p__linq__0
值应该是 '%~_a%'
因为 _
是 LIKE
子句中的特殊字符。
好的,所以我找到了两种不同的解决方案,以在关系包含巨大结果集的情况下始终查询数据库。这两种解决方案都不是直接直观的 - 恕我直言 - 你将需要你以前不需要的 DbContext
变量。
解决方案一是使用 Role
table 作为起点并简单地过滤具有正确 ID 的实体。
注意您不能使用Single
,因为那样您处理的是单个实体对象,您又回到了开始的地方。您需要使用 Where
,然后使用 SelectMany
,即使它违反直觉:
Role role = ...;
string filter = ...;
var filteredRights = context.Role.Where(e => e.RoleId == userRole.RoleId).SelectMany(e => e.Right);
if (!string.IsNullOrWhiteSpace(filter))
{
filteredRights = filteredRights.Where(e => e.RightName.Contains(filter));
}
var rights = filteredRights.ToList();
这导致对数据库的 SQL 查询:
SELECT
[Extent1].[RightName] AS [RightName]
FROM [dbo].[Role_Right] AS [Extent1]
WHERE ([Extent1].[RoleId] = @p__linq__0) AND ([Extent1].[RightName] LIKE @p__linq__1 ESCAPE '~')
-- p__linq__0: '42' (Type = Int32, IsNullable = false)
-- p__linq__1: '%~_a%' (Type = AnsiString, Size = 8000)
我在这里找到的第二个解决方案:
在我的例子中,结果是:
Role role = ...;
string filter = ...;
var filteredRights = context.Entry(userRole).Collection(e => e.Right).Query();
if (!string.IsNullOrWhiteSpace(filter))
{
filteredRights = filteredRights.Where(e => e.RightName.Contains(filter));
}
var rights = filteredRights.ToList();
和SQL
SELECT
[Extent1].[RightName] AS [RightName]
FROM [dbo].[Role_Right] AS [Extent1]
WHERE ([Extent1].[RoleId] = @EntityKeyValue1) AND ([Extent1].[RightName] LIKE @p__linq__0 ESCAPE '~')
-- EntityKeyValue1: '42' (Type = Int32, IsNullable = false)
-- p__linq__0: '%~_a%' (Type = AnsiString, Size = 8000)