使用列表属性通过 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<Guid>)
演员。这是 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<T> == 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的主要内容,如果未能解决你的问题,请参考以下文章