C#实体框架核心:LINQ表达式的处理失败

时间:2020-10-22 08:39:56

标签: c# mysql entity-framework linq

情况

我们的MySQL数据库中有以下表格:

桌式俱乐部(有更多列,但我们现在仅关心ID):

Id | ...
---+----
1  | ...

表格类型:

Id | GenreName
---+-----------
1  | Rock
2  | Classic
3  | Techno

和table club2genre:

ClubId | GenreId
-------+--------
1      | 1
1      | 2
1      | 3

MySQL查询

使用以下MySQL查询:

SELECT
    club.Id,
    GROUP_CONCAT(genre.GenreName) as Music
FROM
    club
INNER JOIN club2genre
    ON club2genre.clubid = club.id
INNER JOIN genre
    ON genre.id = club2genre.genreid
GROUP BY
    club.Id;

我们得到(如预期的那样)此结果:

Id | Music
---+---------
1  | Rock,Classic,Techno

有问题的C#LINQ表达式

var queryResult = DbContext.Club.Join(
        DbContext.Club2Genre,
        club => club.Id,
        club2genre => club2genre.ClubId,
        (club, cg) => new
        {
            Id = club.Id,
            GenreId = cg.GenreId
        })
    .GroupJoin(
        DbContext.Genre,
        temp => temp.GenreId,
        genre => genre.Id,
        (temp, genreList) => new
        {
            ClubId = temp.Id,
            Genres = genreList.Select(genre => genre.Name)
        })
    .GroupBy(result => result.ClubId);

var result = result.ToList();

从理论上讲,此Entity Framework Core LINQ表达式应为上述MySQL查询的1:1转换,其中别名为MusicGROUP_CONCAT)的结果列应为IEnumerable。执行时会引发以下异常:

error

足够有趣的是,它仅在ToList()上调用queryResult时失败,所以我什至不确定LINQ表达式本身就是问题所在。而且(至少对我来说)该错误消息似乎只说了"EF Core might be buggy but also maybe not. Who knows",但似乎并没有特别有用。

所以我的问题是:

1。)使用LINQ和EF Core,我什至可以做些什么吗?

2。)如果是这样,我的代码出了什么问题,为什么崩溃了?

3。)如何解决?

1 个答案:

答案 0 :(得分:0)

使用扩展功能,您可以生成LEFT JOIN并创建展平的结果,然后可以在客户端将其分组以模拟GroupJoin

我假设返回左侧多个副本的单个查询比将左侧和右侧拆分的多个查询更可取,因为这是LINQ to SQL所做的,但是也可以实现一个两个查询,一个查询在左侧,一个查询在与左侧匹配的右侧,然后在客户端上加入。这样消除了重复的左侧,但以两个查询为代价。您还可以通过放置Select来最小化左侧重复项和网络流量,这些{@ 1}}将每一面缩小为仅键和结果所需的列。

public static class QueryableExt {
    // implement GroupJoin for EF Core 3 by using `LEFT JOIN` and grouping on client side
    public static IEnumerable<TRes> GroupJoinEF<TLeft,TRight,TKey,TRes>(
        this IQueryable<TLeft> leftq,
             IQueryable<TRight> rightq,
             Expression<Func<TLeft,TKey>> leftKeyExp,
             Expression<Func<TRight,TKey>> rightKeyExp,
             Func<TLeft, IEnumerable<TRight>, TRes> resFn) =>

        leftq.GroupJoin(rightq, leftKeyExp, rightKeyExp, (left, rightj) => new { left, rightj })
             .SelectMany(lrj => lrj.rightj.DefaultIfEmpty(), (lrj, right) => new { lrj.left, right })
             .AsEnumerable()
             .GroupBy(lr => lr.left, lr => lr.right, (left, rightg) => resFn(left, rightg));
}

使用此扩展名,您的查询将变为:

var queryResult = DbContext.Club
                    .GroupJoinEF(DbContext.Club2Genre.Join(DbContext.Genre, c2g => c2g.GenreId, g => g.Id, (c2g, g) => new { c2g.Id, g.Genre }),
                         c => c.Id,
                         cg => cg.Id,
                         (c, cgj) => new {
                             c.Id,
                             Music = String.Join(",", cgj.Select(cg => cg?.Genre))
                         }
                    );

但是,由于您没有在查询中使用LEFT JOIN,因此您不需要完整的GroupJoin实现,而可以再次使用Join在客户端上进行分组侧面:

var queryResult = DbContext.Club
                    .Join(DbContext.Club2Genre, c => c.Id, c2g => c2g.Id, (c, c2g) => new { c.Id, c2g.GenreId })
                    .Join(DbContext.Genre, cgi => cgi.GenreId, g => g.Id, (cgi, g) => new { cgi.Id, g.Genre })
                    .AsEnumerable()
                    .GroupBy(cg => cg.Id, (cId, cgg) => new { Id = cId, Music = String.Join(",", cgg.Select(cg => cg.Genre)) });