Pomoc przy zapytaniu LINQ

0

Witam,

potrzebuję pomocy przy zapytaniu LINQ... Nie mam kompletnie pomysłu jak to ugryźć. Mam w bazie Naprawy i Serwisantów. Chcę zrobić listę serwisantów z ilością przypisanych do nich napraw.
W SQL zrobiłem coś takiego, co pokazuje mi id, username oraz ilość napraw.

SELECT u.Id, u.Username, count(r.Id) FROM Users u JOIN Repairs r ON r.TechnicianId=u.Id GROUP BY u.Id, u.Username;

W LINQ zdążyłem coś takiego zrobić:

Users.GroupJoin(Repairs, u => u.Id, r => r.TechnicianId, (u, r) => new { Users = u, Repairs = r });

I To mi zwraca użytkownika i obok listę jego napraw, a chciałbym jak powyżej.

W sumie dążę do tego, aby wrzucić wyniki do tego modelu:

public class BestTechniciansReportModel
    {
        public int Id { get; set; }
        public string Firstname { get; set; }
        public string Lastname { get; set; }
        public string Username { get; set; }
        public int NumberOfRepairs { get; set; }
    }

Czy mógłby ktoś coś podpowiedzieć z tym LINQ?

4

Można np tak:

Users.Select(user => new BestTechniciansReportModel {
    Id = user.Id,
    Firstname = user.Firstname,
    Lastname = user.Lastname,
    Username = user.Username,
    NumberOfRepairs = Repairs.Count(repair => repair.TechnicianId == user.Id),
}
0

Fakt, dzięki za pomoc ;)

Ciekaw jestem jak z wydajnością, Twój kod generuje coś podobnego:

SELECT [t3].[Id], [t3].[Firstname], [t3].[Lastname], [t3].[Username], [t3].[value] AS [NumberOfRepairs]
FROM (
    SELECT [t0].[Id], [t0].[Firstname], [t0].[Lastname], [t0].[Username], (
        SELECT COUNT(*)
        FROM (
            SELECT 
                (CASE 
                    WHEN [t1].[TechnicianId] = ([t0].[Id]) THEN 1
                    WHEN NOT ([t1].[TechnicianId] = ([t0].[Id])) THEN 0
                    ELSE NULL
                 END) AS [value]
            FROM [Repairs] AS [t1]
            ) AS [t2]
        WHERE [t2].[value] = 1
        ) AS [value]
    FROM [Users] AS [t0]
    ) AS [t3]
ORDER BY [t3].[value] DESC

Spory potworek :)

1

Zamiast kombinować w LINQ (który jest fajny, ale tylko przy prostych zapytaniach) może prościej będzie po prostu stworzyć odpowiedni widok SQL w bazie danych i z niego pobrać dane za pomocą LINQ. Osobiście unikam tworzenia złożonych zapytań w LINQ bo są mało czytelne, wolę pobrać dane z widoku lub procedury przechowywanej poza tym zapewnia to lepszą wydajność zapytań.

0

@cw: no też pomysł mi się podoba z widokami. Ale tutaj z mojej strony pojawiłyby się kolejne wątpliwości, np. taka, że podczas braku bazy w moim projekcie uruchamia się mój inicjalizer, któy dziedziczy po CreateDatabaseIfNotExists i na początku wywołuję Seed z klasy bazowej, pytanie, czy mi nie stworzy tabeli w bazie o nazwie załóżmy BestTechnicianReportView, bo aby się do tego potem odwoływać z LINQ, to muszę to mieć w klasie DbContext jako nowe DbSet, a z tego Seed tworzy bazę, a nie wiem, czy da się oznaczyć, aby stworzył widok :) No generalnie sporo siedzenia przede mną, myślę, że interesujące, jednak ze względu na czas muszę zostać przy rozwiązaniu LINQowym.

0
lukaszek016 napisał(a):

Spory potworek :)

To jest nic, w robocie nam kiedyś SQL Server wywalił błąd o zbyt głębokim zagnieżdżeniu, jak EF nam wygenerował 1 MB SQLa :D
A tak poważnie to wygląda to trochę słabo, ale bez sprawdzenia planu zapytania ciężko powiedzieć
UPDATE: nie mam doświadczenia z widokami, ale powinno dać się skonfigurować EF żeby wiedział, że coś jest widokiem a nie tabelą.

0
lukaszek016 napisał(a):

Fakt, dzięki za pomoc ;)

Ciekaw jestem jak z wydajnością, Twój kod generuje coś podobnego:

SELECT [t3].[Id], [t3].[Firstname], [t3].[Lastname], [t3].[Username], [t3].[value] AS [NumberOfRepairs]
FROM (
    SELECT [t0].[Id], [t0].[Firstname], [t0].[Lastname], [t0].[Username], (
        SELECT COUNT(*)
        FROM (
            SELECT 
                (CASE 
                    WHEN [t1].[TechnicianId] = ([t0].[Id]) THEN 1
                    WHEN NOT ([t1].[TechnicianId] = ([t0].[Id])) THEN 0
                    ELSE NULL
                 END) AS [value]
            FROM [Repairs] AS [t1]
            ) AS [t2]
        WHERE [t2].[value] = 1
        ) AS [value]
    FROM [Users] AS [t0]
    ) AS [t3]
ORDER BY [t3].[value] DESC

Spory potworek :)

Jesli potrafisz lepiej to sobie napisz zapytanie sql i odpal w ef. I tak zwróci Ci kolekcję określonych obiektów klasy modelu.

0
jacek.placek napisał(a):

Jesli potrafisz lepiej to sobie napisz zapytanie sql i odpal w ef. I tak zwróci Ci kolekcję określonych obiektów klasy modelu.

Napisałem wyżej co wymyśliłem w sql

SELECT u.Id, u.Username, COUNT(r.Id) FROM Users u JOIN Repairs r ON r.TechnicianId=u.Id GROUP BY u.Id, u.Username;

Kwestia uzupełnić selecta o więcej wartości.

Nie napisałem, że wygenerowany kod przez EF jest niewydajny czy coś. Przemyślę kwestię czystego sqla.

1

Ja Cię nie atakuję tylko zwracam uwagę, że w EF można spokojnie używać zapytań SQL-wych.

Czy u Ciebie user ma definiowaną navigation property Repairs?
Jeśli tak to takie coś

Users.Select(user => new BestTechniciansReportModel {
    Id = user.Id,
    Firstname = user.Firstname,
    Lastname = user.Lastname,
    Username = user.Username,
    NumberOfRepairs = user.Repairs.Count(),
}

generuje raczej standardowy dla ormów kod sql

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    (SELECT 
        COUNT(1) AS [A1]
        FROM [dbo].[Repairs] AS [Extent2]
        WHERE [Extent1].[Id] = [Extent2].[UserId]) AS [C1]
    FROM [dbo].[Users] AS [Extent1]

Zresztą wersja podana przez @mad_penguin generuje w LinqPad dokładnie taki sam kod SQL. Nie wiem skąd mas ten swój. EF Core? Brak FK Repair -> User?

1 użytkowników online, w tym zalogowanych: 0, gości: 1