2012-03-12 6 views
5

J'ai la LINQ suivante pour interroger des entités qui a beaucoup de sous-requêtes pour obtenir un peu de données globales:Refactor LINQ to SQL/Entités requête avec plusieurs sous-requêtes

var systems = from s in db.Systems 
       orderby s.Name 
       select new SystemSummary 
       { 
        Id = s.Id, 
        Code = s.Code, 
        Name = s.Name, 
        LastException = (
         from a in s.Applications 
         from e in a.Summaries 
         select e.CreationDate 
       ).Max(), 
        TodaysExceptions = (
         from a in s.Applications 
         from e in a.Summaries 
         where e.CreationDate >= today && e.CreationDate < tomorrow 
         select e 
       ).Count(), 
        /* SNIP - 10-15 more subqueries */        
       }; 

J'écourté la requête pour inclure uniquement les deux des sous-requêtes , mais il pourrait y en avoir 10-15 plus. Existe-t-il un moyen de refactoriser la requête pour nettoyer le code? Je ne cherche pas une augmentation de performance. Je veux juste nettoyer le code en mettant peut-être les sous-requêtes dans des méthodes distinctes tout en s'assurant qu'il s'agit d'un appel unique à la base de données. Est-ce possible?

+0

Si vous voulez un code plus propre, vous pouvez envisager de créer une procédure stockée dans votre base de données – Mathieu

+0

@Mathieu Est-ce la seule façon, cependant? – Dismissile

Répondre

2

Je ne peux offrir minimiser sa longueur par quelque chose comme ceci (en utilisant let mot-clé dans votre requête d'origine):

var subQuery = from a in s.Applications 
        from e in a.Summaries 
        select e; 

vous pouvez aussi avoir des refactors comme:

subQuery.Count(e=>e.CreationDate >= today && e.CreationDate < tomorrow); 

subQuery.max(e=>e.CreationDate); 

En fait utilisez la notation par points et déplacez votre requête vers la fonction associée au lieu de la clause supplémentaire where.

et utiliser subQuery dans votre requête:

  from s in db.Systems 
      orderby s.Name 
      let subQuery = from a in s.Applications 
        from e in a.Summaries 
        select e 
      select new SystemSummary 
      { 
       Id = s.Id, 
       Code = s.Code, 
       Name = s.Name, 
       LastException = subQuery.max(e=>e.CreationDate), 
       TodaysExceptions = subQuery.Count(e=>e.CreationDate >= today 
              && e.CreationDate < tomorrow), 
       /* SNIP - 10-15 more subqueries */        
      }; 

Ceci est encore seul appel à db.

+0

@Dississile, j'ai raté 's', j'avais édité la réponse, vous pouvez utiliser' let' pour simuler cette manière dans votre requête. –

+0

J'aime cette approche. – Dismissile

+0

J'espère que cette aide, agréable aussi voir votre commentaire :) –

0

Il n'y a vraiment aucun problème à séparer votre requête en plusieurs méthodes. Il y a quelques conditions cependant.

Assurez-vous que votre requête est IEumerable. C'est par défaut. IEnumerable garantit que la requête est stockée dans la variable, mais pas exécutée. Le compilateur optimise vos requêtes lors de l'exécution.

rapide et sale exemple:

private MyContext context = new MyContext() 
private IEnumerable<User> getUser(Guid userID) 
{ 
    return context.User.Where(c => c.ID == userID); 
} 

private void evaluateUser() 
{ 
    bool isUserActive getUser().Any(c => c.IsActive) 
} 

Vous pouvez voir que la requête est en deux méthodes. Il n'y a toujours qu'un seul appel à la base de données car un IEnumerable stocke la requête et non le résultat. La requête est exécutée uniquement si nécessaire.

+2

Je pense que vous voulez dire IQueryable, pas IEnumerable.IEnumerable le fera convertir la requête à un objet en mémoire. –

0

Vous pouvez envisager d'utiliser le mot-clé let pour créer des "variables" locales à la requête (cela finit par être vos sous-requêtes). Par exemple:

var systems = from s in db.Systems 
       orderby s.Name 
       let lastException = (from a in s.Applications from e in a.Summaries select e.CreationDate).Max() 
       ... 

Une autre option que vous pouvez faire est de créer éventuellement une sous-requête des différentes associations dès le départ, et de travailler avec ces éléments.

var systems = from s in db.Systems 
       orderby s.Name 
       from summaries in 
        (from ta in s.Applications 
        from te in ta.Summaries 
        ... 
        select { APPS = ta, SUMMS = te ,/*anything else you want*/ }) 
       let lastExpire = (from summaries select SUMMS.CreationDate).Max() 

Enfer, vous pourriez même simplement laisser le laisser dans le deuxième exemple et il suffit d'utiliser les entités summaries dans vos sélections finales. Vous pourriez avoir à jouer avec un peu pour vous assurer que vous n'obtenez pas de duplication de valeurs, mais au moins de cette façon, vous pouvez faire un choix direct contre votre summaries au lieu de réécrire vos sous-requêtes à chaque fois.