【问题标题】:How to use Group By with Task<IEnumerable<Entity>> in LINQ如何在 LINQ 中将 Group By 与 Task<IEnumerable<Entity>> 一起使用
【发布时间】:2020-10-26 04:33:42
【问题描述】:

我是实体框架和 LINQ 的新手。试图通过例子来学习它。 我有一个名为“参与者”的实体,如下所示:

    public class Participant
    {
        public int Id { get; set; }
        public int Count { get; set; }
        public string Zip { get; set; }
        public string ProjectStatus { get; set; }
        public string IncomingSource { get; set; }
    }

我正在尝试使用 Group by 并将结果返回为 Task&lt;IEnumerable&lt;Participant&gt;&gt;。我发现的 Sql 查询是: SELECT Count(Id) as #, Zip FROM [database].[dbo].[Participants] GROUP BY Zip Order By Zip

我试图实现相同结果的代码如下:

    public Task<IEnumerable<Participant>> GetResults()
    {

        var results = context.Participants
        .GroupBy(i => i)
        .Select(i => new { 
            Count = i.Key, 
            Zip = i.Count() 
            }
        ).ToList();

        return results;
    }

但是,这给了我一个转换问题。完整的错误堆栈是: Cannot implicitly convert type 'System.Collections.Generic.List&lt;&lt;anonymous type: project.API.Models.Participant Count, int Zip&gt;&gt;' to 'System.Threading.Tasks.Task&lt;System.Collections.Generic.IEnumerable&lt;project.API.Models.Participant&gt;&gt;'

我不确定如何解决转换这些问题。任何帮助将不胜感激。

【问题讨论】:

    标签: c# sql entity-framework linq ienumerable


    【解决方案1】:

    当您使用 GroupBy 时,您希望创建具有共同点的元素组。您希望共有的属性在参数keySelector 中指定。

    用这个参数你说:请把Paraticipants分组,所有的属性值都与keySelector中指定的属性值相同。

    在您的情况下:您希望创建具有相同 ZIP 值的参与者组。换句话说:如果您获取同一组中的两个 Participants,您希望确定它们的 ZIP 值相同。

    首先,更改您的 keySelector:

    var result = dbContext.Participants.GroupBy(participant => participant.Zip)
    

    结果是一组组。每个组都有一个密钥,每个组都是(没有!)一个参与者序列。所有参与者的 Zip 属性值都等于 Key 的值。

    之后,您想要获取每个组,并从每个组中创建一个新的 Participant 对象,该对象仅填充两个属性

    • Count 是组中参与者的数量

    • Zip 是 Group 中任何元素的 Zip,也就是我们之前看到的 Group 的 Key。

      .Select(groupOfParticipantsWithSameKey => 新参与者 { 计数 = groupOfParticipantsWithSameKey.Count(), Zip = groupOfParticipantsWithSameKey.Key, });

    您是否注意到我用正确的标识符更改了标识符i。选择正确的标识符将帮助您识别 LINQ 中的问题。这可能有点牵连,但它可以帮助您了解您正在处理的序列中的每个元素代表什么。

    顺便说一下Enumerable.GroupBy的重载,就是带参数resultSelector的那个。这使您的 Select 变得不必要。

    var result = context.Participants
        .GroupBy(participanti => participant.Zip,
    
        // parameter resultSelector, take each common Zip, and all Participants that have this Zip
        // to make one new object
        (zip, participantsWithThisZip) => new Participant
        {
            Zip = zip,
            Count = participantsWithThisZip.Count(),
        });
    

    这个更容易理解,因为你去掉了标识符Key。

    一个小的设计改进

    您创建了一个方法,该方法接受所有参与者并为每个使用的 Zip 返回一个参与者,其中 Count 是拥有此 Zip 的参与者的数量。

    如果您将更频繁地使用它,那么最好为此创建一个单独的方法,即IQueryable&lt;Participant&gt; 的扩展方法。这样,您可以对每个参与者序列重用该方法,而不仅仅是数据库中的所有参与者。见Extension Methods demystified

    public static class ParticpantExtensions
    {
        public static IQueryable<Participant> ToParticipantsWithSameZip(
            this IEnumerable<Participant> participants)
        {
            return participants.GroupBy(
            participanti => participant.Zip,
            (zip, participantsWithThisZip) => new Participant
            {
                Zip = zip,
                Count = participantsWithThisZip.Count(),
            });
        }
    }
    

    用法:

    你原来的方法:

    Task<IList<Participant>> FetchParticipantsWithSameZipAsync() 
    {
        using (var dbContext in new MyDbContext(...))
        {
            return await dbContext.ToParticipantsWithSameZip().ToListAsync();
        }
    }
    

    您可以在非异步版本中重复使用它:

    IList<Participant>> FetchParticipantsWithSameZipAsync() 
    {
        using (var dbContext in new MyDbContext(...))
        {
            return dbContext.ToParticipantsWithSameZip().ToList();
        }
    }
    

    但现在你也可以将它与其他 LINQ 方法交织在一起:

    var newYorkParticipantsWithSameZip = dbContext.Participants
        .Where(participant => participant.State == "New York")
        .ToParticipantsWithSameZip()
        .OrderBy(participant => participant.Count())
        .ToList();
    

    几个优点:

    • 可重复使用
    • 代码看起来更干净,
    • 更容易理解它的作用
    • 您可以在没有数据库的情况下对其进行单元测试:任何IQueryable&lt;Participant&gt; 都可以。
    • 如果您需要更改ToParticipantsWithSameZip,您只需更改一处并重写测试。

    因此,如果您将在多个地方使用它:请考虑扩展方法

    【讨论】:

    • 这很有教育意义,也很有帮助。谢谢!
    【解决方案2】:

    两种最简单的方法是从方法签名中删除Task,使方法同步

    public IEnumerable<Participant> GetResults()
    

    或者,如果您希望方法使用 async 和 await 模式,请在方法签名中使用 async 关键字 并调用 awaitToListAsync()

    public async Task<IEnumerable<Participant>> GetResults()
    {
        var results = await context.Participants
            .GroupBy(i => i)
            .Select(i => new { 
                Count = i.Key, 
                Zip = i.Count() 
                }
            ).ToListAsync();
    

    注意:在这种情况下,您可能需要重命名方法GetResultsAsync

    【讨论】:

      【解决方案3】:

      正如 TheGeneral 所说.. 使用异步和重命名方法:

      public async Task<IEnumerable<Participant>> GetResultsAsync()
      {
          return context.Participants
          .GroupBy(i => i.Zip)
          .Select(i => new Participant 
           { 
              Count = i.Count(), 
              Zip = i.Zip 
              }
          ).ToListAsync();
      }
      

      【讨论】:

        【解决方案4】:

        选择就像你想从你的特定查询中提取什么。 在这里,您正在使用 new {...}

        创建一个匿名类型
        .Select(i => new { 
                    Count = i.Key, 
                    Zip = i.Count() 
                    }
        

        这就是它生产的原因 列出匿名>'这个类型。

        但是你想要 List 那么怎么办?匿名类型返回参与者。 像这样

        .Select(i => new Participant 
             { 
                Count = i.Count(), 
                Zip = i.Zip 
                }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-03-15
          • 1970-01-01
          • 2022-10-22
          • 2023-04-08
          • 1970-01-01
          • 2010-09-23
          • 2017-10-11
          相关资源
          最近更新 更多