Jon*_*ood 7 c# entity-framework entity-framework-core asp.net-core
我正在寻找有关如何编写查询的建议。对于每一个Goal,我想选择第一Task(排序Task.Sequence),除了与任何任务ShowAlways == true。(我的实际查询更复杂,但此查询演示了我遇到的限制。)
我试过这样的事情:
var tasks = (from a in DbContext.Areas
from g in a.Goals
from t in g.Tasks
let nextTaskId = g.Tasks.OrderBy(tt => tt.Sequence).Select(tt => tt.Id).DefaultIfEmpty(-1).FirstOrDefault()
where t.ShowAlways || t.Id == nextTaskId
select new CalendarTask
{
// Member assignment
}).ToList();
Run Code Online (Sandbox Code Playgroud)
但是这个查询似乎太复杂了。
var tasks = (from a in DbContext.Areas
from g in a.Goals
from t in g.Tasks
let nextTaskId = g.Tasks.OrderBy(tt => tt.Sequence).Select(tt => tt.Id).DefaultIfEmpty(-1).FirstOrDefault()
where t.ShowAlways || t.Id == nextTaskId
select new CalendarTask
{
// Member assignment
}).ToList();
Run Code Online (Sandbox Code Playgroud)
问题是线路let nextTaskId =...。如果我注释掉,就没有错误。(但我不明白我想要什么。)
我很乐意承认我不了解错误消息的详细信息。关于我能想到的解决此问题的唯一其他方法是返回所有Tasks,然后在客户端对它们进行排序和过滤。但我的偏好是不检索我不需要的数据。
任何人都可以看到任何其他方法来处理此查询吗?
注意:我使用的是最新版本的 Visual Studio 和 .NET。
更新:
我尝试了一种不同但效率较低的方法来处理此查询。
var tasks = (DbContext.Areas
.Where(a => a.UserId == UserManager.GetUserId(User) && !a.OnHold)
.SelectMany(a => a.Goals)
.Where(g => !g.OnHold)
.Select(g => g.Tasks.Where(tt => !tt.OnHold && !tt.Completed).OrderBy(tt => tt.Sequence).FirstOrDefault()))
.Union(DbContext.Areas
.Where(a => a.UserId == UserManager.GetUserId(User) && !a.OnHold)
.SelectMany(a => a.Goals)
.Where(g => !g.OnHold)
.Select(g => g.Tasks.Where(tt => !tt.OnHold && !tt.Completed && (tt.DueDate.HasValue || tt.AlwaysShow)).OrderBy(tt => tt.Sequence).FirstOrDefault()))
.Distinct()
.Select(t => new CalendarTask
{
Id = t.Id,
Title = t.Title,
Goal = t.Goal.Title,
CssClass = t.Goal.Area.CssClass,
DueDate = t.DueDate,
Completed = t.Completed
});
Run Code Online (Sandbox Code Playgroud)
但这也产生了一个错误:
System.InvalidOperationException: 'Processing of the LINQ expression 'Where<Task>(
source: MaterializeCollectionNavigation(Navigation: Goal.Tasks (<Tasks>k__BackingField, DbSet<Task>) Collection ToDependent Task Inverse: Goal, Where<Task>(
source: NavigationExpansionExpression
Source: Where<Task>(
source: DbSet<Task>,
predicate: (t) => Property<Nullable<int>>((Unhandled parameter: ti).Inner, "Id") == Property<Nullable<int>>(t, "GoalId"))
PendingSelector: (t) => NavigationTreeExpression
Value: EntityReferenceTask
Expression: t
,
predicate: (i) => Property<Nullable<int>>(NavigationTreeExpression
Value: EntityReferenceGoal
Expression: (Unhandled parameter: ti).Inner, "Id") == Property<Nullable<int>>(i, "GoalId"))),
predicate: (tt) => !(tt.OnHold) && !(tt.Completed))' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.'
Run Code Online (Sandbox Code Playgroud)
这是一个很好的例子,需要完全可重现的例子。当尝试使用类似的实体模型重现该问题时,我要么得到一个不同的错误DefaulIfEmpty(-1)(显然不支持,不要忘记删除它 - SQL 查询将在没有它的情况下正常工作)或在删除它时没有错误。
然后我注意到与我的错误消息相比,您的错误消息中有一个深藏不露的小差异,这使我找到了问题的原因:
MaterializeCollectionNavigation(Navigation: Goal.Tasks (<Tasks>k__BackingField, DbSet<Task>)
Run Code Online (Sandbox Code Playgroud)
特别是DbSet<Task>在最后(在我的情况下是ICollection<Task>)。我意识到,你所使用DbSet<T>类型的集合导航属性,而不是通常的ICollection<T>,IEnumerable<T>,List<T>等,如
public class Goal
{
// ...
public DbSet<Task> Tasks { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
不要那样做。DbSet<T>是一个特殊的 EF Core 类,应该仅DbContext用于表示 db 表、视图或原始 SQL 查询结果集。更重要DbSet的是,s 是唯一真正的 EF Core 查询根,因此这种用法会混淆 EF Core 查询转换器也就不足为奇了。
所以把它改成一些支持的接口/类(例如, ICollection<Task>),原来的问题就解决了。
然后删除DefaultIfEmpty(-1)将允许成功翻译有问题的第一个查询。
我没有启动并运行 EF Core,但是你能像这样拆分它吗?
var allTasks = DbContext.Areas
.SelectMany(a => a.Goals)
.SelectMany(a => a.Tasks);
var always = allTasks.Where(t => t.ShowAlways);
var next = allTasks
.OrderBy(tt => tt.Sequence)
.Take(1);
var result = always
.Concat(next)
.Select(t => new
{
// Member assignment
})
.ToList();
Run Code Online (Sandbox Code Playgroud)
编辑:抱歉,我不太擅长查询语法,也许这可以满足您的需要?
var allGoals = DbContext.Areas
.SelectMany(a => a.Goals);
var allTasks = DbContext.Areas
.SelectMany(a => a.Goals)
.SelectMany(a => a.Tasks);
var always = allGoals
.SelectMany(a => a.Tasks)
.Where(t => t.ShowAlways);
var nextTasks = allGoals
.SelectMany(g => g.Tasks.OrderBy(tt => tt.Sequence).Take(1));
var result = always
.Concat(nextTasks)
.Select(t => new
{
// Member assignment
})
.ToList();
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
447 次 |
| 最近记录: |