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.NameList&lt;String&gt; 中。

List&lt;String&gt; 包含关键字列表,而不是确切的部门名称

Department 表有这种行:

IdDepartment Name
1 Administration
2 HHRR
3 Sales
4 Marketing

List&lt;String&gt; 可以是任何关键字,如“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=&gt; 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&lt;User&gt;().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 过滤器翻译的主要内容,如果未能解决你的问题,请参考以下文章

EF Core 数据过滤

EF Core 如何向 .ThenIncludes 添加过滤器

EF Core 中实现 动态数据过滤器

EF Core 全局查询过滤器复杂表达式

过滤包含在 EF Core 中

EF Core 5检查过滤器中的所有ID是不是存在于相关实体中