Sytuacja

Mamy następujące stoły w naszej bazie danych MySQL:

Klub stołowy (istnieje więcej kolumn, ale dbamy tylko o identyfikator):

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

Gatunek stołowy:

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

I stołowy Club2Genre:

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

Zapytanie MySQL.

Korzystanie z następujących kwerendy 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;

Dostajemy (zgodnie z oczekiwaniami) Ten wynik:

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

(Buggy) C # Linq wyrażenie

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();

W teorii Ten podmiot ramy rdzenia rdzenia rdzenia LINQ powinien być tłumaczeniem 1: 1 zapytania MySQL powyżej, gdzie kolumna wyników, która jest aliasowana jako Music (GROUP_CONCAT Jedena) powinna być IeMerableHOWever po wykonaniu następującego Wyjątek jest rzucony:

error

Co ciekawe, ale nie powiedzie się tylko po wywołaniu ToList() na queryResult, więc nie jestem nawet pewien, że sama ekspresja LINQ jest problemem. Oraz (przynajmniej dla mnie) Komunikat o błędzie nie wydaje się być szczególnie pomocny inny niż zasadniczo mówienie "EF Core might be buggy but also maybe not. Who knows".

Więc moje pytania to:

1.) Jest to, co próbuję zrobić nawet możliwe przy użyciu linq i rdzenia EF?

2.) Jeśli tak, co jest nieprawidłowe mój kod i dlaczego się rozbija?

3.) Jak to naprawić?

1
Frederik Hoeft 22 październik 2020, 11:39

1 odpowiedź

Najlepsza odpowiedź

Korzystając z funkcji rozszerzenia, możesz wygenerować LEFT JOIN i utwórz spłaszczony wynik, który można następnie pogrupować na stronie klienta, aby naśladować GroupJoin.

Zakładam, że pojedyncze zapytanie zwraca wiele kopii po lewej stronie, jest korzystne dla wielu zapytań podziału po lewej i prawej stronie, jak to właśnie działa Linq do SQL, ale możliwe jest również wdrożenie tłumaczenia, które wykonuje dwa zapytania, jeden po lewej stronie i jeden po prawej stronie, gdzie pasuje do lewej strony, a następnie dołącz do klienta. Eliminuje to duplikat po lewej stronie na koszt dwóch zapytań. Można również zminimalizować lewe powielanie i ruch sieciowy, umieszczając w Select s, który zwęża się z każdą stroną w dół tylko do kolumn potrzebnych do kluczy i wyniku.

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));
}

Dzięki temu rozszerzeniu twoje zapytanie staje się:

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))
                         }
                    );

Jednak ponieważ w zapytaniu nie używasz LEFT 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)) });
0
NetMage 23 październik 2020, 00:28