LINQ查询问题:从每个目标中选择第一个任务

时间:2019-11-27 19:38:18

标签: c# entity-framework asp.net-core entity-framework-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();

但是此查询似乎太复杂了。

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.'

6 个答案:

答案 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);