在Linq查询中,我能否选择多个对象?

40

我能在select语句中返回多个项目吗?例如,我有一组Fixture(类似足球比赛或美式足球比赛的fixture)。每场fixture都包括主队和客队以及主队和客队的得分。我想获取所有平局的队伍。我想使用类似于以下的东西:

IEnumerable<Team> drew = from fixture in fixtures
                         where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
                         select fixture.HomeTeam && fixture.AwayTeam;

我知道这个语法是不正确的,但我不知道是否有可能这样做。我需要两个查询然后将它们连接起来吗?

编辑:这只是为了学习,没有特定的实现要求。基本上,在这个阶段我只想要一个已经抽签的团队列表。例如,对于给定的比赛列表,我可以找到所有已经抽签的团队,以便通过1分(3胜0负)更新他们在表格中的排名。

7个回答

34

101 LINQ Samples,即Select - 匿名类型1

... select new { HomeTeam = fixture.HomeTeam, AwayTeam = fixture.AwayTeam };

不是他想要的答案。他想要一个团队列表,而不是一个带有主队和客队属性的匿名类型列表。 - Mike Powell
这是真的...我可以使用匿名类型来解决它...只是想知道是否有一种方法可以获取团队列表。如果这是唯一的方法,那就是唯一的方法。 - James Hay
我同意这并没有返回一个团队列表,但我认为最好让他调整代码以支持处理这种匿名类型。如果James Hay可以更新他的问题来描述他的用途,那可能会更有帮助。 - bendewey
我认为他的问题已经完美地描述了他的要求:“我想获得一份抽签团队的清单。” 这里有很多原因他可能不想在这里使用匿名类型(需要将列表传递到此方法之外是常见原因之一)。 - Mike Powell
自原始帖子发布以来,元组已经被添加到.Net/C#中,并且可能是许多匿名类型可用的选项。https://blogs.msdn.microsoft.com/mazhou/2017/05/26/c-7-series-part-1-value-tuples/ - codybartfast
对于这个Linq查询,生成的SQL是内部连接。 - Shaiju T

34
下面的代码将返回一个IEnumerable<Team>:
IEnumerable<Team> drew =
    from fixture in fixtures
    where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
    from team in new[]{fixture.HomeTeam, fixture.AwayTeam}
    select team;

或者,使用流畅的LINQ风格:

IEnumerable<Team> drew =
    fixtures
    .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
    .SelectMany(fixture => new[]{fixture.HomeTeam, fixture.AwayTeam});

展开和 FlatMap

这个要求通常称为“展开”。也就是说,将一个<包含<事物的集合的集合>>转换为一个<事物的集合>。

SelectMany既映射(将夹具映射到团队数组)又展平(将团队数组序列展平为团队序列)。它类似于其他语言(如Java和JavaScript)中的“flatMap”函数。

可以分离映射和展平:

IEnumerable<Team> drew =
    fixtures
    .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
    // map 
    .Select(fixture => new[]{fixture.HomeTeam, fixture.AwayTeam})
    // flatten
    .SelectMany(teams => teams);

其他方法

迭代器块

使用迭代器块也可以实现相同的效果,但我认为这很少是最佳方法:

IEnumerable<Team> Drew(IEnumerable<Fixture> fixtures){
    var draws = 
      fixtures
      .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore));

    foreach(var fixture in draws){
        yield return fixture.HomeTeam;
        yield return fixture.AwayTeam;
    }
}

联合

联合(Union)也是一种选项,但有可能会产生不同于上面的结果:

  1. 结果的顺序将会不同。所有主场比赛的结果都将先返回,然后是所有客场比赛的结果。

  2. Union 枚举了两次固定比赛(fixtures),因此,取决于固定比赛的实现方式,在调用之间有更新固定比赛的潜力。例如:如果在调用之间添加了新的平局比赛,则可能会返回客队但不返回主队。

正如Mike Powell所描述的那样:

IEnumerable<Team> drew =
    ( from fixture in fixtures
      where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
      select fixture.HomeTeam
    ).Union(
      from fixture in fixtures
      where fixture.Played  && (fixture.HomeScore == fixture.AwayScore)
      select fixture.AwayTeam );

根据 fixtures 的来源/实现方式,考虑“缓存”已经绘制的 fixtures,以避免需要两次枚举 fixtures。

var draws = 
    ( from fixture in fixtures
      where fixture.Played  && (fixture.HomeScore == fixture.AwayScore)
      select fixture
    ).ToList();

IEnumerable<Team> drew =
    (from draw in draws select draw.HomeTeam)
    .Union(from draw in draws select draw.AwayTeam);

或者使用流畅风格:

var draws = 
    fixtures
    .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
    .ToList();

IEnumerable<Team> drew =
    draws.Select(fixture => fixture.HomeTeam)
    .Union(draws.Select(fixture => fixture.AwayTeam));

修改Fixture类

可以考虑将“ParticipatingTeams”添加到Fixture类中,如下所示:

IEnumerable<Team> drew =
    from fixture in fixtures
    where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
    from team in fixture.ParticipatingTeams
    select team;

但正如@MattDeKrey指出的那样,这需要更改合同。

代码示例

代码示例可在Repl.it上找到。


3
对于你的第一个查询点赞,它不需要更改合同,比领先答案更有效率。 - Matt DeKrey

23

我认为你正在寻找如下的Union方法:

IEnumerable<Team> drew = (from fixture in fixtures
                     where fixture.Played 
                        && (fixture.HomeScore == fixture.AwayScore)
                     select fixture.HomeTeam)
                     .Union(from fixture in fixtures
                     where fixture.Played 
                        && (fixture.HomeScore == fixture.AwayScore)
                     select fixture.AwayTeam);

1
是否有任何LINQ关键字等效于.Union()方法? - Nikhil Girraj
@NikhilGirraj 不,我认为没有。'Union'不是LINQ关键字之一:https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/query-keywords而且,如果它存在的话,它应该已经在这个示例中使用了:https://learn.microsoft.com/zh-cn/dotnet/framework/data/adonet/sql/linq/return-the-set-union-of-two-sequences - codybartfast
对于这个 Linq 查询,生成的 SQL 是内部连接吗? - Shaiju T

14

自己试着做了一下,结果跟“这要看情况”一样。

使用查询推导语法:

IEnumerable<Team> drew =
    from fixture in fixtures
    where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
    from team in new[]{fixture.AwayTeam, fixture.HomeTeam}
    select team;
使用 lambda 表达式与扩展方法:
IEnumerable<Team> drew =
    fixtures.Where(f => f.Played && f.HomeScore == f.AwayScore)
    .SelectMany(f => new[]{f.HomeTeam, f.AwayTeam});

编辑:我不知道在你的数据库中是否可能有一支团队曾经玩过并且打了不止一次平局,但如果可能的话,那么你可能需要使用Distinct查询运算符:

编辑:我不知道在您的数据库中是否可能有一支团队曾经进行多次比赛并打平多次,但如果是这样的话,您可能需要使用Distinct查询运算符:

IEnumerable<Team> drew =
    (from fixture in fixtures
     where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
     from team in new[]{fixture.AwayTeam, fixture.HomeTeam}
     select team).Distinct();
或:
IEnumerable<Team> drew =
    fixtures.Where(f => f.Played && f.HomeScore == f.AwayScore)
    .SelectMany(f => new[]{f.HomeTeam, f.AwayTeam})
    .Distinct();

1
上一个示例对我很有用。它还教会了我可以使用类型推断来声明数组。每一天都是一个学校日。 - Gusdor

6

或者你可以定义一个类型来保存所有的数据:

IEnumerable<TeamCluster> drew = from fixture in fixtures
                         where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
                         select new TeamCluster {
                             Team1 = fixture.HomeTeam,
                             Team2 = fixture.AwayTeam,
                             Score1 = fixture.HomeScore,
                             Score2 = fixture.AwayScore
                         };

class TeamCluster {
    public Team Team1 { get; set; }
    public Team Team2 { get; set; }
    public int Score1 { get; set; }
    public int Score2 { get; set; }
}

5

编辑:抱歉,之前误解了你的问题,所以重新撰写答案。

您可以使用 "SelectMany" 运算符来实现您想要的功能:

IEnumerable<Team> drew =
           (from fixture in fixtures
            where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
                  select new List<Team>()
                             { HomeTeam = fixture.HomeTeam,
                               AwayTeam = fixture.AwayTeam
                             }).SelectMany(team => team);

这将返回一个扁平化的抽奖团队列表。

1

我遇到了这个问题,找不到我想要的东西,所以我写了一个小的扩展方法来实现我想要的功能。

public static IEnumerable<R> MapCombine<M, R>(this IEnumerable<M> origList, params Func<M, R>[] maps)
{
    foreach (var item in origList)
    foreach (var map in maps)
    {
        yield return map(item);
    }
}

根据问题描述,你可以像这样做:
var drew = fixtures.Where(fixture => fixture.Played && 
                        (fixture.HomeScore == fixture.AwayScore))
                    .MapCombine(f => f.HomeTeam, f => f.AwayTeam);

有趣的是,Intellisense 对此并不完全满意,在下拉菜单的顶部没有 lambda 表达式,但在“=>”之后它就很满意了。但最重要的是编译器很满意。

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