我正在寻找有关如何编写查询的建议。除了使用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();
但是此查询似乎太复杂了。
System.InvalidOperationException: 'Processing of the LINQ expression 'OrderBy<Task, int>(
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: (t0) => Property<Nullable<int>>((Unhandled parameter: ti0).Outer.Inner, "Id") == Property<Nullable<int>>(t0, "GoalId"))
PendingSelector: (t0) => NavigationTreeExpression
Value: EntityReferenceTask
Expression: t0
,
predicate: (i) => Property<Nullable<int>>(NavigationTreeExpression
Value: EntityReferenceGoal
Expression: (Unhandled parameter: ti0).Outer.Inner, "Id") == Property<Nullable<int>>(i, "GoalId"))),
keySelector: (tt) => tt.Sequence)' 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.'
问题出在第let nextTaskId =...
行。如果我将其注释掉,则没有错误。 (但我不明白我的追求。)
我很容易承认我不了解错误消息的详细信息。我能想到的唯一另一种方法是返回所有Task
,然后在客户端上对其进行排序和过滤。但是我更喜欢不检索不需要的数据。
任何人都可以通过其他方式查看此查询吗?
注意:我使用的是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
});
但这也会产生错误:
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.'
答案 0 :(得分:5)
这是一个很好的例子,需要完整的示例。当尝试使用类似的实体模型重现该问题时,我遇到了关于DefaulIfEmpty(-1)
的其他错误(显然不受支持,请不要忘记删除它-SQL查询可以正常使用而不行),或者没有错误。
然后,我注意到您的错误消息与我的消息相比有一个很小的深层隐藏差异,这导致了问题的原因:
MaterializeCollectionNavigation(Navigation: Goal.Tasks (<Tasks>k__BackingField, DbSet<Task>)
最后是DbSet<Task>
(在我的情况下是ICollection<Task>
)。我意识到您将DbSet<T>
类型用于集合导航属性,而不是通常的ICollection<T>
,IEnumerable<T>
,List<T>
等,例如
public class Goal
{
// ...
public DbSet<Task> Tasks { get; set; }
}
根本不要那样做。 DbSet<T>
是一个特殊的EF Core类,应该仅用于DbContext
中以表示数据库表,视图或原始SQL查询结果集。更重要的是,DbSet
是唯一真正的EF Core查询 roots ,因此这种用法会混淆EF Core查询转换器也就不足为奇了。
因此,将其更改为某些受支持的接口/类(例如ICollection<Task>
),即可解决原始问题。
然后删除DefaultIfEmpty(-1)
将允许成功翻译有问题的第一个查询。
答案 1 :(得分:3)
我没有启动并运行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();
编辑:对不起,我对查询语法不太满意,也许这符合您的需求?
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();
答案 2 :(得分:0)
我建议您首先将此查询分为几个部分。尝试使用您的Goals
逻辑遍历foreach
中的Task
。将每个新的CalendarTask
添加到您事先定义的列表中。
总体上打破这种逻辑并进行一些试验可能会使您对Entity Framework Core的局限性有所了解。
答案 3 :(得分:0)
我认为我们可以将查询分为两个步骤。首先,查询每个目标并获取最小序列任务并将其存储(也许使用匿名类型,例如{NextTaskId,Goal})。然后,我们查询临时数据并获得结果。例如
Areas.SelectMany(x=>x.Goals)
.Select(g=>new {
NextTaskId=g.Tasks.OrderBy(t=>t.Sequence).FirstOrDefault()?.Id,
Tasks=g.Tasks.Where(t=>t.ShowAlways)
})
.SelectMany(a=>a.Tasks,(a,task)=>new {
NextTaskId = a.NextTaskId,
Task = task
});
答案 4 :(得分:0)
我尝试创建linq请求,但不确定结果
var tasks = ( from a in DbContext.Areas
from g in a.Goals
from t in g.Tasks
join oneTask in (from t in DbContext.Tasks
group t by t.Id into gt
select new {
Id = gt.Key,
Sequence = gt.Min(t => t.Sequence)
}) on new { t.Id, t.Sequence } equals new { oneTask.Id,oneTask.Sequence }
select new {Area = a, Goal = g, Task = t})
.Union(
from a in DbContext.Areas
from g in a.Goals
from t in g.Tasks
where t.ShowAlways
select new {Area = a, Goal = g, Task = t});
答案 5 :(得分:0)
我目前没有EF Core,但是您真的需要对此进行比较吗?
查询任务是否足够?
如果定义了导航属性或外键,则可以使用以下方式进行成像:
Tasks.Where(task => task.Sequence == Tasks.Where(t => t.GoalIdentity == task.GoalIdentity).Min(t => t.Sequence) || task.ShowAlways);