1
votes

Convertir SQL en Linq avec EF Core

J'utilise .NET Core 2.2, EF Core, C # et SQL Server 2017. Je ne parviens pas à traduire la requête dont j'ai besoin en Linq.

Voici la requête que je dois convertir:

 public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int YearOfRelease { get; set; }
    public string Genre { get; set; }
    public int RunningTime { get; set; }
    public IList<Rating> Ratings { get; set; }
}

public class Rating
{
    public int Id { get; set; }
    public int MovieId { get; set; }
    public int UserId { get; set; }
    public decimal RatingValue { get; set; }
}

L'idée de la requête précédente est d'obtenir le Top 5 des films en fonction de la note moyenne, en les classant d'abord par la moyenne la plus élevée, et en cas de même ordre moyen par ordre alphabétique.

Jusqu'à présent, c'est ma requête qui fait la jointure, mais qui manque toujours: le grouper par, moyenne et ordre:

public class MovieRepository : IMovieRepository
{
    private readonly MovieDbContext _moviesDbContext;
    public MovieRepository(MovieDbContext moviesDbContext)
    {
        _moviesDbContext = moviesDbContext;
    }

    public IEnumerable<Movie> GetTopFive()
    {
        var result = _moviesDbContext.Movies.OrderByDescending(x => x.Id).Take(5).
                     Include(x => x.Ratings);

        return result;
    }
}

Et voici les entités:

SELECT      TOP 5
            p.Id, 
            p.Title, 
            AVG(q.RatingValue) AvgRating
FROM        Movies AS p
INNER JOIN  Ratings AS q ON p.Id = q.MovieId
GROUP BY    p.Id, p.Title
ORDER BY    AvgRating DESC, p.Title ASC

J'ai essayé d'utiliser l'outil Linqer également pour convertir ma requête en Linq, mais cela ne fonctionnait pas.

J'apprécierai toute aide pour convertir cette requête en LINQ pour la méthode "GetTopFive".

Merci p >


6 commentaires

Vous attendez-vous au top 5 ou juste au top 5?


Les mieux notés 5. Ainsi, un film peut avoir une ou plusieurs lignes dans le tableau de classement. Par exemple, 10 utilisateurs pourraient noter Terminator 1, si, pour ce film, la moyenne de ces notes est le maximum par rapport aux autres films, doit être affichée en premier.


Attendre! Je mets à jour la réponse


envisagez également de regrouper les lignes de la note et obtenez la moyenne.


Ouais! mais j'essaye une solution différente.


Vérifiez ma réponse! J'ai mis à jour la réponse. J'ai vérifié que cela fonctionne parfaitement.


3 Réponses :


1
votes

Essayez ce qui suit:

public class Rating
{
    public int Id { get; set; }
    public int MovieId { get; set; }
    public int UserId { get; set; }
    public decimal RatingValue { get; set; }

    public Movie Movie { get; set; }
}

Cela exclura les films sans classement.

Mais si vous faites comme suit (comme la réponse d'artista_14):

public IEnumerable<Movie> GetTopFive()
{
    var result = _moviesDbContext.Movies.GroupBy(x => new { x.Id, x.Title })
        .Select(x => new {
            Id = x.Key.Id,
            Title = x.Key.Title,
            Average = x.Average(y => y.Ratings.Sum(z => z.RatingValue))
    }).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();
    return result;
}

cela inclura également les films sans classement.

Remarque: je vois que votre classe de modèle Rating ne contient aucun Propriété de navigation du film . Veuillez ajouter ceci comme suit:

public IEnumerable<Movie> GetTopFive()
{
    var result = _moviesDbContext.Ratings.GroupBy(r => r.MovieId).Select(group => new
        {
            MovieId = group.Key,
            MovieTitle = group.Select(g => g.Movie.Title).FirstOrDefault(),
            AvgRating = group.Average(g => g.RatingValue)
        }).OrderByDescending(s => s.AvgRating).Take(5).ToList();
    return result;
}


11 commentaires

S'il vous plaît laissez-moi savoir si cela fonctionne ou non! Sinon, veuillez me faire savoir le message d'erreur.


J'ai eu une erreur disant: System.ArgumentException: "Au moins un objet doit implémenter IComparable."


Oh! Je soupçonne ça! Attendez s'il vous plaît!


Si votre contrôleur est API Controller, assurez-vous que l'attribut [Produces ("application / json")] est présent au-dessus de votre contrôleur.


ne fonctionne pas encore, obtention de ce sql: SELECT [g.Movie0]. [Id], [g.Movie0]. [Title] FROM [Movies] AS [g.Movie0]


J'ai dû faire une conversion pour IEnumerable


Continuons cette discussion dans le chat .


@ MarcosF8 Vérifiez le chat s'il vous plaît


Aidez-vous demain s'il vous plaît!


Merci beaucoup TanvirArjel, gros effort pour tenter de résoudre le problème de votre côté, j'apprécie vraiment votre aide!


La requête que vous avez utilisée échouera dans certaines circonstances, comme je l'ai décrit dans le commentaire de cette réponse.



2
votes

Essayez celui-ci -

var data = _moviesDbContext.Movies.Include(x => x.Ratings)
            .Select(x => new {
                Id = x.Id,
                Title = x.Title,
                Average = (int?)x.Ratings.Average(y => y.RatingValue)
        }).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();


5 commentaires

Votre requête précédente était correcte. Cela lancera La séquence ne contient aucun élément. si le film n'a pas encore de note! En dehors de cela, il y a un autre problème dans cette requête. Vous n'avez pas utilisé .Include (m => m.Ratings) .


@TanvirArjel Merci pour vos commentaires. Je suis également nouveau sur linq et lambda. J'interroge directement Movie Table, alors ne pensez pas que dans la table Movie utilisant .GroupBy (x => new {x.Id, x.Title}) n'a aucune signification, c'est pourquoi je l'ai supprimé. Incluant également les données de notation ici n'est pas nécessaire car il ne veut que la note moyenne qui sera calculée en DB uniquement .. ??


Sans inclure les notes sera nul! Par conséquent, jettera une exception.


Le Inclure n'est pas nécessaire car x.Ratings fait déjà partie de la requête. La seule chose est qu'il devrait être (int?) X.Ratings.Average (y => y.RatingValue) afin que les films sans classement puissent avoir une valeur null . Ensuite, je pense que cela devrait être correct, démontrant qu'un groupby LINQ n'est pas nécessaire. @ MarcosF8 Ce serait bien d'avoir un peu plus de retours que "ne fonctionne pas" :).


@GertArnold Je suis d'accord



0
votes

et enfin c'est le code qui fonctionne bien:

public class MovieRepository : IMovieRepository
{
    private readonly MovieDbContext _moviesDbContext;
    public MovieRepository(MovieDbContext moviesDbContext)
    {
        _moviesDbContext = moviesDbContext;
    }

    public IEnumerable<Movie> GetAll()
    {
        return _moviesDbContext.Movies;
    }

    public IEnumerable<MovieRating> GetTopFive()
    {
        var result = _moviesDbContext.Movies.Include(x => x.Ratings)
                    .Select(x => new MovieRating
                    {
                        Id = x.Id,
                        Title = x.Title,
                        Average = x.Ratings.Average(y => y.RatingValue)
                    }).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();

        return result;
    }
}

public class MovieRating
{
    public int Id { get; set; }
    public string Title { get; set; }
    public decimal Average { get; set; }
}

Le problème était de créer un type anonyme dans le select, donc cette ligne résout le problème: .Select (x => new MovieRating

Et voici le code complet de la méthode et de la nouvelle classe que j'ai créée pour mapper les champs de sélection avec un type concret:

var data = _moviesDbContext.Movies.Include(x => x.Ratings)
            .Select(x => new MovieRating
            {
                Id = x.Id,
                Title = x.Title,
                Average = x.Ratings.Average(y => y.RatingValue)
            }).OrderByDescending(x => x.Average).ThenBy(x => x.Title).Take(5).ToList();

        return data;


0 commentaires