EF Core SQL 过滤器翻译
Posted
技术标签:
【中文标题】EF Core SQL 过滤器翻译【英文标题】:EF Core SQL Filter Translation 【发布时间】:2021-03-29 20:42:26 【问题描述】:我正在使用带有 ASP NET 5.0.1 Web API 的 EF Core 5.0.1,我想使用 LinqKit.Microsoft.EntityFrameworkCore 5.0.2.1
构建带有 PredicateBuilder
的查询
出于问题的目的,我将模型简化为:
public class User
public long IdUser get; set;
public string Name get; set;
public virtual ICollection<UserDepartment> UserDepartments get; set;
public class Department
public long IdDepartament get; set;
public string Name get; set;
public virtual ICollection<UserDepartment> UsersDepartment get; set;
public UserDepartment
public long IdUser get; set;
public long IdDepartment get; set;
public virtual Department get; set;
public virtual User get; set;
一个User
可以有多个Departaments
,一个Departament
可以有多个Users
三个模型在 SQL Server 中都有对应的表和一个 IEntityTypeConfiguration
类,并设置了适当的关系。
我想要实现的只是搜索属于任何Departament
的任何User
,其中Department.Name
在List<String>
中。
List<String>
包含关键字列表,而不是确切的部门名称
Department
表有这种行:
IdDepartment | Name |
---|---|
1 | Administration |
2 | HHRR |
3 | Sales |
4 | Marketing |
List<String>
可以是任何关键字,如“Admin”、“Sal”、“Mark”等。
第一次尝试
... 是建立一个这样的谓词:
List<string> kwDepartments = new List<String> "mark","admin";
var predicate = PredicateBuilder.New<User>(true);
predicate = predicate.And(x => x.UserDepartments.Where(y => kwDepartments.Any(c => y.Department.Name.Equals(c))).Any());
这会产生一个带有 IN 运算符的 SQL,如下所示:
...[t].[Name] IN (N'mark', N'admin'))
显然这不是我想要的,但如果我使用.Contains
而不是.Equals
,则会引发异常
无法翻译 LINQ 表达式
我认为这是因为我正在尝试评估非原始值。
第二次尝试
... 是迭代 kwDepartments
并为每个字符串添加一个 .Or
,如下所示:
foreach (string dep in kwDepartments )
predicateDep = predicateDep.Or(x => x.UserDepartments.Where(y=> y.Department.Name.Contains(dep)).Any());
predicate = predicate.And(predicateDep);
这会返回我期望的匹配,但也有两个问题。
SQL 转换性能不佳,因为 EF Core 为每个关键字设置了 INNER JOIN
。不管kwDepartment
有两个或三个元素,但100个或1000个元素都是不允许的。
Departments
表上的每个 INNER JOIN
都有一个 SELECT
包含所有表字段,我不需要它们。我尝试在谓词中添加 .Select(x=> x.Name)
语句以仅使用两个字段,但没有效果。
第三次尝试
... 正在使用 EF.Functions.FreeText
进行全文搜索,但似乎没有任何区别。
我的目标是建立一个谓词,翻译成类似于:
SELECT [c.IdUser]. [c.Name]
FROM [User] AS [c]
WHERE EXISTS (
SELECT 1
FROM [UserDepartment] AS [u]
INNER JOIN (
SELECT [c0].[IdDepartment], [c0].[Name] <--ONLY NEED TWO FIELDS INSTEAD OF ALL FIELDS
FROM [Department] AS [c0]
) AS [t] ON [u].[IdDepartment] = [t].[IdDepartment]
WHERE ([c].[IdUser] = [u].[IdUser]) AND ([t].[Name] like (N'admin%') or [t].[Name] like (N'mark%')))
LIKE
运算符不是强制性的,但我放在那里是为了更好地理解。
再次感谢!
【问题讨论】:
【参考方案1】:您在Or
谓词的正确轨道上,但不是user.UserDepatments.Any(single_match)
上的多个Or
谓词,您应该创建单个基于Or
的谓词以在单个user.UserDepatments.Any(multi_or_match)
中使用。
类似这样的:
var departtmentPredicate = kwDepartments
.Select(kw => Linq.Expr((Department d) => EF.Functions.Like(d.Name, "%" + kw + "%")))
.Aggregate(PredicateBuilder.Or);
然后
predicate = predicate.And(u => u.UserDepartments
.Select(ud => ud.Department) // navigate to department
.AsQueryable() // to be able to use departtmentPredicate expression directly
.Any(departtmentPredicate));
使用该代码和示例列表,DbSet<User>().Where(predicate)
被翻译成如下内容:
DECLARE @__p_1 nvarchar(4000) = N'%mark%';
DECLARE @__p_2 nvarchar(4000) = N'%admin%';
SELECT [u].[IdUser], [u].[Name]
FROM [User] AS [u]
WHERE EXISTS (
SELECT 1
FROM [UserDepartment] AS [u0]
INNER JOIN [Department] AS [d] ON [u0].[IdDepartment] = [d].[IdDepartament]
WHERE ([u].[IdUser] = [u0].[IdUser]) AND (([d].[Name] LIKE @__p_1) OR ([d].[Name] LIKE @__p_2)))
【讨论】:
非常感谢伊万。您的解决方案就像一个魅力! :) 只有一件事是完美的。产生您的解决方案的 SQL 包含一个 SELECT,其中包含我不需要的 UserDepartment 的 INNER JOIN 中的所有字段(我的 UserDepartment 模型有 15 个字段),只需要 IdDepartment 和名称。有没有办法只从 SQL 中获取这两个字段? 实际上你可以看到它加入了 2 个表而没有专门选择任何字段,注意select 1 from ...
我的错,正在检索额外的字段,因为我在 IEntittypeConfiguration 类上有一个额外的查询谓词。您的解决方案非常完美,再次感谢 Ivan!以上是关于EF Core SQL 过滤器翻译的主要内容,如果未能解决你的问题,请参考以下文章