使用列表属性通过 LINQ 过滤 dbset

Posted

技术标签:

【中文标题】使用列表属性通过 LINQ 过滤 dbset【英文标题】:Using a list property to filter a dbset with LINQ 【发布时间】:2021-12-28 22:59:30 【问题描述】:

假设我有一个包含员工任务列表的活动:

public class Event()

  public Guid? StaffId  get; set; 


public class StaffTask()

  public Guid StaffId  get; set; 
  public Guid TaskId  get; set; 

在获取工作人员列表的所有事件的情况下,我该如何做这样的事情?

var staffTasks = new List<StaffTasks>() 
 
  new StaffTask ()  StaffId = "guid1", TaskId = "guid2" ,
  new StaffTask ()  StaffId = "guid3", TaskId = "guid4" 
;

queryable = _db.Events.AsQueryable()
  .Where(event => 
      staffTasks.Any(st => st.StaffId == event.StaffId)
  );

我目前在运行上述程序时遇到此错误:

The LINQ expression 'DbSet<Event>()
    .Where(e => __staffTasks
        .Any(or => (Nullable<Guid>)or.StaffId == e.StaffId))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.

我们的目标是让这里只返回第二个和第三个事件

var events = new List<Event>() 
  new Event()  StaffId = null ,
  new Event()  StaffId = "guid1" ,
  new Event()  StaffId = "guid2" ,
  new Event()  StaffId = "guid20" ,
  new Event()  StaffId = null 

【问题讨论】:

问题几乎肯定是(Nullable&lt;Guid&gt;) 演员。这是 Entity Framework Core,对吗? 正确。我错误地擦洗了我的对象并刚刚更新了它们,尽管我认为你的观点是正确的,我的事件中的 Staffid 需要可以为空,因为在人为的情况下,事件不一定需要工作人员 我还添加了一个示例事件列表,作为搜索用例可能是合理的 FWIW,我只是尝试使道具不可为空,但同样的事情正在发生 Any 不适用于本地集合(有小例外)。使用Contains 或像这样的解决方案FilterByItems 【参考方案1】:

我仍然不确定为什么@Prasad 的回答不起作用

EF 希望使用您提供的 C# 并从中生成 SQL。它知道如何做一些事情,但不是所有事情

当您有“c# 中的集合,在列中搜索集合中的值”的模式时,EF 将希望创建类似WHERE id IN(value1,value2..) 的 SQL,但关键是它不会去挖掘和运行复杂的预测来获取该列表价值观

Any 将起作用,但(据我所知)仅适用于只是被搜索值类型的集合。这意味着将您的 StaffTasks 投影到一个简单的 Guid?集合也可以作为 Any:

var staff = staffTasks.Select(st => (Guid?)st.StaffId).ToArray();

_db.Events
  .Where(event => staff.Any(st => event.StaffId == st ));

EF 可以将它翻译成一个 IN,就像它可以包含一样,但是记住“不要使用任何,使用包含”可能更好的原因是因为如果你执行某些 EF,Contains 会更好地导致编译错误不会容忍的。

这不会编译(注意我使用了 staffTasks.Contains):

_db.Events
  .Where(event => staffTasks.Contains(event.StaffId));

所以 Contains 会自动引导您使用原语列表,而 Any 会让您在 LINQ 中思考“我将在 lambda 模式中提取我想要的道具”并写道:

_db.Events
  .Where(event => staffTasks.Any(st => event.StaffId == st.SomeProp));

这将在 c# 中编译,但不会转换为 EF,因为 EF 必须运行投影以获取它想要放入 IN 中的值。它还尝试在这里执行nullable&lt;T&gt; == T (StaffTask 和 Event 对 StaffId 有不同的类型),这是在本地评估的 Any 中的合法 C#,但 EF 不翻译的另一件事

--

所以最后,你的答案被翻译成COALESCE(event.StaffId, '00000000-0000-0000-0000-000000000000') IN ('guid1', 'guid3')(我猜3是一个错字,因为你的样本数据是guid2)这很好

如果您对列表进行类型匹配,使其充满Guid?,您可以删除??Guid.Empty,它会被翻译为event.StaffId IN ('guid1', 'guid3'),这也很好(它会丢弃事件中的null StaffId ) 并且实际上可能更快,因为 COALESCE 可以排除索引的使用

如果您使用 Guid? 的列表与 Any 它也可以工作..

..但一般来说,如果你使用 Contains,你会更频繁地第一次解决这些事情,因为 Contains 要求你提供东西的方式更经常符合 EF 需要接收东西的方式

【讨论】:

【参考方案2】:

这似乎完成了工作,尽管我仍然不确定为什么@Prasad 的回答不起作用

var staffTasks = new List<StaffTasks>() 
 
  new StaffTask ()  StaffId = "guid1", TaskId = "guid2" ,
  new StaffTask ()  StaffId = "guid3", TaskId = "guid4" 
;

var staff = staffTasks.Select(st => st.StaffId).ToList();

queryable = _db.Events.AsQueryable()
  .Where(event => staffTasks.Contains(event.StaffId ?? Guid.Empty));

【讨论】:

以上是关于使用列表属性通过 LINQ 过滤 dbset的主要内容,如果未能解决你的问题,请参考以下文章

加入内存时,LINQ 查询中的“位置”是不是重要?

使用 LINQ 过滤列表

C#LINQ Group通过自定义属性的多个字段

扩展属性中的 Odata 过滤不起作用

在 DbSet<T> 上使用 LINQ 扩展方法时调用不明确

LINQ to SQL查询基于使用外键与使用内置导航属性成功或失败