EF核心SQL过滤器转换

Pablo7747

首先,对不起我的英语,并感谢您阅读我的问题。

我将EF Core 5.0.1和ASP NET 5.0.1 Web API一起PredicateBuilder使用,并且我想使用来构建查询LinqKit.Microsoft.EntityFrameworkCore 5.0.2.1

为了这个问题,我将模型简化为:

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建立了适当关系类。

所有我想要实现的是搜索任何User属于任何DepartamentDepartment.Name是一个List<String>

List<String>包含关键字的列表,没有确切的部门名称

Department 表具有这种行:

Id部门 名称
1个 行政
2 HRHRR
3 营业额
4 营销学

并且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);

这将返回我期望的匹配,但也有两个问题。

  1. 由于EF CoreINNER JOIN为每个关键字添加,SQL转换的性能很差无论kwDepartment具有两个或三个元素,但具有100或1000个元素都是不允许的。

  2. 每个INNER JOINDepartments表上都有一个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运算符不是强制性的,但是为了更好的理解,我把它放在了那里。

再次感谢!

伊万·斯托夫

您可以使用Or谓词走上正确的道路,但您应该创建一个基于单个谓词的内部谓词,以代替在该谓词中使用多个Or谓词user.UserDepatments.Any(single_match)Oruser.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)))

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章