当使用GroupBy时,Linq中的Include无法工作

34

include matchparticipants不起作用。调试时总是显示Null。 但是当我注释掉GroupBy后,它可以正常工作。 我正在使用Entity Framework 4.3.1和Code-First。

实体:

public class Match
    {
        [ScaffoldColumn(false)]
        public int MatchId { get; set; }

        [Required(ErrorMessage = "Matchtype is a required field")]
        public int Scheme { get; set; }

        [Required]
        [DefaultValue(false)]
        public bool Finished { get; set; }

        public int Round { get; set; }


        // Relations
        [Required]
        public Category Category { get; set; }

        public Official Official { get; set; }

        public Slot Slot { get; set; }

        public ICollection<MatchParticipant> MatchParticipants { get; set; }
    }

 public class MatchParticipant
    {
        [ScaffoldColumn(false)]
        public int MatchParticipantId { get; set; }

        public int Points { get; set; }

        public int Goals { get; set; }

        [Required]
        public Match Match { get; set; }

        [Required]
        public Team Team { get; set; }
    }

public class Team
    {
        [ScaffoldColumn(false)]
        public int TeamId { get; set; }

        [Required(ErrorMessage="Name is a required field")]
        public string Name { get; set; }

        [Required(ErrorMessage="Number of players is a required field")]
        public int NumberOfPlayers { get; set; }

        [Required(ErrorMessage="Coach is a required field")]
        public string Coach { get; set; }

        [Required(ErrorMessage="Phone is a required field")]
        public string Phone { get; set; }

        public string CellPhone { get; set; }

        public string Fax { get; set; }

        [Required(ErrorMessage="Email is a required field")]
        public string Email { get; set; }

        [Required(ErrorMessage="Address is a required field")]
        public Address Address { get; set; }

        public Pool Pool { get; set; }

        [Required(ErrorMessage = "Category is a required field")]
        public Category Category { get; set; }

        public ICollection<MatchParticipant> matchParticipants { get; set; }
    }

        var matches =
        context.matches
       .Include("Official")
       .Include("Slot")
       .Include("MatchParticipants.Team")
       .Include("Category.Tournament")
       .Where(m => m.Category.Tournament.TournamentId == tournamentId)
       .GroupBy(m => m.Category);

如何使Include工作?

5个回答

56

Include 要求查询的形状不变。这意味着你的查询必须返回 IQueryable<Match>GroupBy 运算符可能被认为是改变查询形状的,因为它返回 IQueryable<IGrouping<TKey, TSource>>。一旦查询的形状发生变化,所有的 Include 语句都将被省略。因此,你不能在投影、自定义连接和分组中使用 Include

作为解决方法,可以在 Linq-to-objects 中执行分组:

var matches = context.matches
                     .Include("Official")
                     .Include("Slot")
                     .Include("MatchParticipants.Team")
                     .Include("Category.Tournament")
                     .Where(m => m.Category.Tournament.TournamentId == tournamentId)
                     .ToList()
                     .GroupBy(m => m.Category);

编辑:如评论和其他答案中提到的,这是一种非常危险的变通方法,可能会导致性能问题。它将所有记录从数据库拉到应用程序中,并在应用程序中进行聚合。它在某些情况下可以工作,但绝对不适用于通用解决方案。

Edit: 如评论和其他答案中提到的,这是一种非常危险的变通方法,可能会导致性能问题。它将所有记录从数据库拉到应用程序中,并在应用程序中进行聚合。它在某些情况下可以工作,但绝对不适用于通用解决方案。


3
当您必须保持对象类型为IQueryable<>时,是否有其他建议? - Scott Alexander
2
此查询未经过优化,因为它首先从数据库中获取所有行,然后在LINQ to Objects中进行“group by”操作。 - mehrdad khosravi
4
@khosravi 您是正确的。这种方法会导致性能灾难。我感到惭愧,因为我曾经提出过这个解决方案,却没有非常清楚地表达它。 - Ladislav Mrnka

37

在这种特殊情况下,当您的GroupBy是最新操作符时,此查询效果良好...但在我看来,上面的答案对于初学者来说是最糟糕的答案,因为它会导致查询非常糟糕的优化,当您的GroupBy没有立即执行,而是在其他语句(Where,Select...)之后执行时。

var ctx = new MyDataContext(); // Please use "using"
var result = ctx.SomeTable
                //.Include(ah => ah.IncludedTable) // DO NOT PUT IT HERE
                .Where(t => t.IsWhateverTrue)
                .GroupBy(t => t.MyGroupingKey)
                .Select(gt => 
                    gt.OrderByDescending(d => d.SomeProperty)
                        .FirstOrDefault(ah => ah.SomeAnotherFilter))
                .Include(ah => ah.IncludedTable) // YES, PUT IT HERE
                .ToList(); // Execute query here

5
别忘了:使用 System.Data.Entity;。 - Tomino
1
我可以确认这种方法是可行的,也就是在查询语句的结尾处添加 include。请注意,在 include 之前可能需要添加 Distinct。 - Robert Jørgensgaard Engdahl
8
在这种情况下,结果是否只包含每个组中的第一个元素?如何在分组后包含所有元素,以便所有组的所有元素都保留在结果中? - Kornelije Petak
1
仍然对我无效 - 出现InvalidOperationException - “Include已在非实体可查询上使用” - AMG
1
就记录而言:当前稳定的EF版本(7.0.10)不支持此查询形式。 - Gert Arnold
显示剩余5条评论

3
GroupBy() 后,通过在 .Select() 中指定包含的内容,将它们包含在结果中。
var result = ctx.SomeTable
                .Where(t => t.IsWhateverTrue)
                .GroupBy(t => t.MyGroupingKey)
                .Select(g => new
                  {
                    Date = g.Key.Value,
                    Reservations = g.Select(m => new
                        {
                          m,
                          m.IncludedTable // this selects/includes the related table
                        })
                  }
                );

-1

我找不到一种可行的方法来使用Entity Framework在SQL端进行分组,然后在.net站点上进行Include()。另一种选择是编写自己的SQL查询并进行自己的映射。实际上,这并不难:

  1. 在Nuget包管理器控制台中运行Install-Package dapper以进行实体映射

  2. 定义您要映射的自定义对象(或使用现有对象)

    class MyClass { int userID; int numRows;}
    
  3. 针对您的上下文对象进行查询:

    List<MyClass> results = _context.Database.GetDbConnection().Query<MyClass>("SELECT u.id as userID, count(1) as numRows FROM Users u inner join Table2 t2 on u.id= t2.userID  group by u.id;").ToList();
    

似乎你必须编写自己的代码 - 这不是真的。即使是 EF Core,在很大程度上放弃了 GroupBy 支持,也支持带聚合的 GroupBy。 - Gert Arnold
嗨,Gert。我可能应该说“如果你想加入和聚合”而不是“如果你想聚合”。因为我认为 ef 模型绑定不支持在 sql 端同时进行连接和聚合。这就是 Ladislav 上面回答的全部意义。 - Jack
不,重点是查询的形状会改变。然后,他们甚至不想聚合,他们想要“包含”,所以这一切都无关紧要。此外,EF也会使用联接查询进行聚合,为什么不呢? - Gert Arnold
很简单。这个问题不涉及聚合。我不明白你为什么在这里提起它。 - Gert Arnold
在某种程度上,你是正确的。这里讨论的更恰当地描述为分组而不是聚合。我认为可以说,在任何列上进行分组实际上意味着你正在对该列进行聚合,因为你正在从具有相同键的多行中选择单个键值。但是,我已经编辑了问题,删除了任何使用“聚合”一词的用法。希望这能够澄清答案。 - Jack
显示剩余5条评论

-1
您可以在 GroupBy() 之后的 .Select() 中实现Include,例如:
var query = ctx.Table
   .Where(x => "condition")
   .GroupBy(g => g.GroupingKeyId)
   .Select(x => new
   {
       GroupingKeyId = x.Key.GroupingKeyId,
       GroupingKey = x.Select(y => y.GroupingKey)
            .FirstOrDefault(y => y.Id == x.Key.GroupingKeyId)
   });

x.Key.GroupingKeyId语法错误。还有Include在哪里?话虽如此,我真的不认为需要在现有答案中添加任何内容。 - Gert Arnold
@GertArnold 这里的GroupingKey是一个用于分组的属性的占位符。根据问题的参考,GroupingKeyId可以是MatchIdMatchParticipantId,而GroupingKey可以是MatchMatchParticipant。通过这种方式分配GroupingKey会自动包含该属性,并且在像@Benjamin的答案中需要将其分配给集合时,只需跳过FirstOrDefault即可。 - JBuG
我早餐吃LINQ。试试用类似g => g.GroupingKeyId的实际GroupBy,你就会明白我的意思了。还有,Include在哪里? - Gert Arnold
这是我的工作代码:if (queryType == QueryType.Summary) { query = query.GroupBy(g => new { g.DataDate, g.HierarchyNodeId }) .Select(x => new MetricData { DataDate = x.Key.DataDate, HierarchyNodeId = x.Key.HierarchyNodeId, HierarchyNode = x.Select(y => y.HierarchyNode).FirstOrDefault(y => y!.Id == x.Key.HierarchyNodeId), // 包括 Created = _environmentService.CreateUtcDateTime() }); } - JBuG
那是我的工作代码,它证明了你错误地认为x.Key是唯一有效的代码。g.HierarchyNodeIdg.GroupingKeyId的实现。所以,我们应该放弃来回争论。我的代码正在运行,并且“包含”了我需要的虚拟属性。 - JBuG
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接