2010-11-04 26 views
0

J'ai une requête que je tente de porter de SQL (T-SQL) vers LINQ-to-Entities 4.0 (C#). Le jeu de résultats contient une combinaison de "lignes de détail" standard et d'informations "statistiques" globales.Sélection de statistiques agrégées (groupées) avec des enregistrements dans une requête LINQ-to-Entities

SQL d'origine utilisé une norme gauche, sélectionnez-jointe à l'information globale, semblable à ceci:

SELECT 
    UserId, 
    Name, 
    Email, 
    ISNULL(Stats.TotalPosts, 0) as TotalPosts, 
    Stats.LastPost 
FROM Users 
LEFT OUTER JOIN 
(
    SELECT UserId, COUNT(*) as TotalPosts, MAX(DatePosted) as LastPost 
    FROM Articles 
    GROUP BY UserId 
) as Stats ON Stats.UserId = Users.UserID 

A gauche join est utilisé plutôt que les sous-requêtes dans l'instruction SELECT pour des raisons de performance - plus d'un agrégat statistique est renvoyée (nombre total de messages et date du dernier message)

J'ai rencontré un succès partiel en le convertissant en une requête LINQ vers Entités en C# 4.0, mais je ne suis pas entièrement sûr de savoir comment la jointure devrait faire un lien avec l'énoncé du groupe. J'imagine que je pense à cela en termes de SQL et ne pas utiliser correctement LINQ.

J'ai eu un certain succès les statistiques de rupture dans une requête distincte:

var stats = 
(
    from a in entities.Articles 
    group a by a.UserId into g 
    select new 
    { 
     UserId = g.Key, 
     TotalPosts = g.Count(), 
     LastUpdated = g.Max(i => i.DatePosted) 
    } 
); 

var query = 
(
    from u in entities.Users 
    join s in stats on u.UserId equals s.UserId 
    orderby u.Name 
    select new UserListing() 
    { 
     UserId = u.UserId, 
     Name = u.Name, 
     Email = u.Email, 
     TotalPosts = s.TotalPosts, 
     LastUpdated = s.LastUpdated 
    } 
); 

Malheureusement, la jointure des dans les filtres requête LINQ tous les utilisateurs qui ne sont pas soumis tous les articles. Passer à l'équivalent d'une jointure externe en incluant DefaultIfEmpty provoque d'autres problèmes - Je peux seulement retourner "null" pour TotalPosts au lieu de 0. Même avec "TotalPosts = (s.TotalPosts == null)? 0: s. TotalPosts "dans select, une exception est levée sauf si la propriété TotalPosts est nullable. Quelles sont les meilleures pratiques pour combiner des lignes de détail et des informations agrégées de cette manière?

Merci!

Répondre

1

Essayez ceci:

var query = 
(
    from u in entities.Users 
    join s in stats on u.UserId equals s.UserId into g 
    from a in g.DefaultIfEmpty() 
    orderby u.Name 
    select new UserListing() 
    { 
     UserId = u.UserId, 
     Name = u.Name, 
     Email = u.Email, 
     TotalPosts = a.TotalPosts, 
     LastUpdated = a.LastUpdated 
    } 
); 
0

Pour obtenir un externe vous adhérez devez utiliser DefaultIfEmpty. Pour résoudre le problème null, vous pouvez essayer

TotalPosts = s.TotalPosts.GetValueOrDefault(), 

ou si s.TotalPosts en quelque sorte ne se présente pas comme int? vous pouvez essayer hacks comme

TotalPosts = ((int?)s.TotalPosts).GetValueOrDefault(0), 
+0

Je n'ai pas réussi à compiler. Mes connaissances LINQ ne sont pas très bonnes, mais GetValueOrDefault n'est pas une méthode d'extension de "int" ou "int?" – ShadowChaser

1

Une option que vous avez est de vous assurer que le propriétés appropriées dans la requête stats sont nullable. LINQ-to-entities fera les ajustements nécessaires pour que cela fonctionne dans la mesure du possible. Ensuite, effectuez la jointure externe gauche comme d'habitude.

var stats = 
(
    from a in entities.Articles 
    group a by a.UserId into g 
    select new 
    { 
     UserId = g.Key, 
     TotalPosts = (int?)g.Count(), 
     LastUpdated = g.Max(i => i.DatePosted) 
    } 
); 

var query = 
(
    from u in entities.Users 
    join s in stats on u.UserId equals s.UserId into joinedStats 
    from s in joinedStats.DefaultIfEmpty() // do left outer join 
    orderby u.Name 
    select new UserListing() 
    { 
     UserId = u.UserId, 
     Name = u.Name, 
     Email = u.Email, 
     TotalPosts = s.TotalPosts, // null if doesn't contain stats 
     LastUpdated = s.LastUpdated // default DateTime if doesn't contain stats 
    } 
); 
+0

Cela aide, mais j'ai toujours le problème de TotalPosts étant nul au lieu de zéro (et l'exigence qu'il soit tapé comme "int?". Y at-il un moyen de résoudre ce problème dans la requête, et aussi rouler "stats "Dans la même déclaration? – ShadowChaser

+0

' TotalPosts' sera de type 'int?' et aura la valeur de 'null' si l'utilisateur correspondant n'a pas de données. Requête 'stats' avec le principal mais c'est plus propre comme je l'ai fait en ce moment –

+0

@ShadowChaser: Le fonctionnement de linq n'est pas un avantage pour rouler des requêtes ensemble. ne peut pas améliorer les performances.Les avoir séparés est en fait plus clair. – Hogan