我认为这里有一些需要改进的地方。
包含并不用于过滤.
这根本不是这样做的正确地点。
Include
是为了自动检索所有链接的实体。既然你不想all的实体(您只想要一个子集),您不应该使用Include
.
或者,您仍然可以使用Include
,只要您愿意删除不需要的条目在记忆中(即在它们被加载之后)。但我想你不希望这样。
相反,您可以使用显式Select
陈述。举个简单的例子:
context.Projects
.Where(p => p.Id == projectId)
.Select(p => new ConsultantSearchResult() {
Project = p,
ConsultantsNamedBob = p.Consultants.Where(c => c.FirstName == "Bob")
}).ToList();
请注意,缺少Include
。正如我之前所说,Include
是用来自动地(并且隐式地)加载相关数据。但因为你明确地中注明您想要的数据Select
,不再需要隐式包含。 EF 将会满足您的需求。
Your Select
不直观
我认为你所期待的东西与你得到的东西不同。看代码:
return context.Timesheets //1
.Where(...) //2
.Select(t => t.Project) //3
看看会发生什么:
- 您选择所有时间表。
- 您过滤时间表并留下时间表的子集
- 您将获得项目列表每个时间表的.
如果您的过滤(第 2 步)留给您multiple来自同一项目的时间表,然后.Select(t => t.Project)
会给你multiple同一项目的实例。那不好。
这里有两个例外:
- You know您将总共找到一张时间表。但那么你应该使用
First
, Single
, FirstOrDefault
or SingleOrDefault
。你应该只使用Where
如果您有可能获得多个结果。
- You expect more than one timesheet, but you know that you'll never find two timesheets from the same project (thus never creating duplicates when you call the
Select
). I would assume (by reading the entity names) that it's possible for a specific consultant to have multiple timesheets for the same project, but maybe that's not true.
- 如果我的推断是正确的,那么在执行以下操作后,您将遇到重复项目的问题
Select
.
- 如果我的推论不正确,那么我预计时间表和顾问之间的关系会更牢固,因为每个项目顾问都会有 1 个(或没有)时间表,永远不会超过 1 个。但是您当前的数据结构缺乏时间表之间的任何真正关系和顾问。
一个快速的解决方案是使用Distinct
:
return context.Timesheets
.Where(...)
.Select(t => t.Project)
.Distinct()
但我个人认为更好的解决方案是反向查找:从项目开始,过滤项目在他们的时间表上(而不是过滤时间表):
return context.Projects
.Include(p => p.Timesheets)
.Where(p => p.Timesheets.Any(t => t.UserId == userId && ...))
.ToList();
这排除了重复项目的问题。请注意,这还没有解决您的“过滤包含”问题。
同一上下文中的单独查询
评论中也提到了这一点。这是一个可行的选择,但我发现这是一种肮脏的方法,会创建不直观的代码。
一个简单的例子 https://stackoverflow.com/questions/11917926/loading-related-entities-in-separate-queries-and-sorting-of-children-collection:
context.Configuration.LazyLoadingEnabled = false;
var parent = context.Set<Entity>().First(e => e.Name = "ABC");
// Load relations in separate query
context.Set<Child>()
.Where(c => c.Parent.Name == "ABC")
.OrderBy(c => c.Name) // You can at least try it but as mentioned above it may not work in all scenarios
.Load();
// Now parent.Children collection should be filled
该示例使用OrderBy
代替Where
,但两者的工作方式相同。
即使您分别查询子级和父级,它们的导航属性也会不断更新,因为您在同一上下文中运行查询。
这对你来说是一个可行的选择,但我对这段代码有点担心,因为它绝不是readable第二个查询会改变第一个查询的结果。
对我来说,这感觉同样肮脏。具有业务逻辑get
or set
的财产。它可以工作,但会导致意外的行为,并使调试变得非常困难。
请注意,可能会很清楚to you幕后发生了什么,但不同的开发人员在查看代码时很容易掩盖它。
我个人不喜欢这样,但你的意见可能会有所不同。
您不完整的数据结构使其变得复杂。
查看您的代码示例,我认为您的数据一致性存在一些问题。您正在使用userId
在两个地方进行过滤:
- 时间表:
t => t.UserId == userId
- 顾问:
c => c.UserId == userId
如果时间表与顾问相关,那么这两个实体之间应该存在关系。目前,您的项目有一个时间表列表和一个顾问列表,但时间表和顾问之间没有明显的关系。
这就是为什么你的查找很复杂。你试图嘲笑一种不存在的关系。
如果这种关系确实存在,那么查找所有内容就会容易得多:
return context.Timesheets
.Include(t => t.Project)
.Include(t => t.Project.Account)
.Include(t => t.Consultant)
.Where(t => t.Consultant.UserId == userId && t.SubjectDate == date && t.WorkingDaySchedules.Count() > 0)
.ToList()
然后你就得到了你正在寻找的东西。您不再需要单独执行两个操作userId
检查后,您不再需要“手动同步”假关系,查找过程更加简化和可读。
小评论
也许有些你还不知道的事情。你可以重写
t.WorkingDaySchedules.Count() > 0
as
t.WorkingDaySchedules.Any() //is there at least one item in the collection?
额外的好处是,如果需要,您可以添加过滤器:
t.WorkingDaySchedules.Any(wds => wds.IsActive) //is there at least one item in the collection that meets the condition?