2010-06-26 5 views
1

J'ai une exigence apparemment simple, mais je ne peux pas comprendre comment l'écrire comme une requête qui a seulement un aller-retour au serveur.Linq requête agrégée par mois actif

Fondamentalement, j'ai une simple table

CREATE TABLE Item 
(
    id int not null identity(1,1), 
    create datetime not null, 
    close datetime --null means not closed yet 
); 

et ce que je veux faire est sur une plage de temps (disons 1/1/2010 à 6/1/2010), pour chaque mois j'ai besoin du nombre d'articles actifs pendant ce mois. un élément est actif s'il a été créé pendant ou avant ce mois et qu'il n'est pas fermé (c'est-à-dire fermé est nul) ou a été fermé après ce mois. Donc, je traduis que dans une expression LINQ en utilisant une méthode d'assistance:

//just returns the first day of every month inbetween min and max (inclusive) 
private IEnumerable<DateTime> EnumerateMonths(DateTime Min, DateTime Max) 
{ 
    var curr = new DateTime(Min.Year, Min.Month, 1); 
    var Stop = new DateTime(Max.Year, Max.Month, 1).AddMonths(Max.Day == 1 ? 0 : 1); 
    while(curr < Stop) 
    { 
     yield return curr; 
     curr = curr.AddMonths(1); 
    } 
} 

public List<DataPoint> GetBacklogByMonth(DateTime min, DateTime max) 
{ 
    return EnumerateMonths(min, max) 
     .Select(m => new DataPoint 
         { 
          Date = m, 
          Count = DB.Items.Where(s => s.Create <= m.AddMonths(1) && (!s.Close.HasValue || s.Close.Value >= m.AddMonths(1))) 
            .Count() 
          } 
      ).ToList(); 
} 

qui fonctionne parfaitement, sauf que chaque Count est une requête séparée de sorte que son super lent (un aller-retour pour chaque mois), donc ma question est de savoir comment pourrait Je restructurer cette requête pour le faire dans un aller-retour au serveur. Au départ, je pensais à faire une sorte de groupe par agrégat par mois, mais parce que chaque élément pourrait être «actif» dans de nombreux mois, je ne pense pas que cela fonctionnerait.

Des suggestions?

Répondre

0

Je déteste pour répondre à ma propre question, mais voici ce que j'ai fait.

Ce que j'avais vraiment besoin de faire tout le long était de me joindre à une table de mois puis de faire un groupe et de compter le nombre d'articles pour chaque mois. un regroupement normal sur un mois ne fonctionnerait pas parce que les articles ne seraient comptés que dans un seul mois et pas tous ceux pour lesquels ils étaient actifs. J'ai donc ajouté une table Mois contenant juste les dates du premier mois et j'ai fait une jointure gauche dessus. Cette opération doit être faite assez souvent que j'ai pensé qu'il valait la peine d'ajouter une table pour cela.

Heres la requête finale:

 var joins = from m in DB.Months 
        from s in DB.Items 
        let nm = m.month.AddMonths(1) 
        where s.Create < nm && (!s.Close.HasValue || s.Close.Value >= nm) && m.month >= min && m.month <= max 
        select new { d = m.month, id = s.ID }; 
     var counts = from j in joins 
        group j by j.d into g 
        select new DataPoint { Date = g.Key, Count = g.Key > DateTime.Now ? 0 : g.Count() }; 

J'ai aussi ajouté un code pour vous assurer que les mois a les lignes correctes dans pour ma requête.

0

Il suffit de tirer vos objets d'abord, puis rouler sur vos mois en utilisant la collection en mémoire. Je ne suis pas sûr que j'ai vos critères droit pour la requête db, mais il serait essentiellement:

var items = Db.Items.Where(s => s.Create <= min 
    && (!s.Close.HasValue || s.Close.Value >= max)).ToList(); 

return EnumerateMonths(min, max).Select(m => new DataPoint 
    { 
     Date = m, 
     Count = items.Where(s => s.Create <= m.AddMonths(1) && (!s.Close.HasValue || s.Close.Value >= m.AddMonths(1))).Count() 
    }).ToList(); 
+0

cela devrait fonctionner mais il est possible qu'il puisse tirer plusieurs milliers de lignes, cela peut être trop de trafic réseau. Mal essayer et voir si c'est acceptable. – luke

+0

Si les lignes sont grandes, vous pouvez 'Select()' juste les colonnes 'Create' et' Close'. Une autre alternative, si vous utilisez MS SQL Sever, est de créer une fonction CLR qui fera le travail côté serveur. Vous pouvez ensuite ajouter cette fonction en tant que méthode dans votre mappage LINQ-to-SQL. http://msdn.microsoft.com/en-us/library/ms189876.aspx – Jay

0

J'irais avec ce que dit Jay. J'ai eu une situation similaire. Faire votre tri/requête en mémoire fonctionnerait plus vite que de cliquer plusieurs fois sur DB.

Si vous savez à l'avance que vous allez seulement faire une lecture, définissez votre objectContext.Table-MergeOption.NoTracking et itérer utilisant une boucle foreach.

Si vous avez encore besoin de suivi, détacher l'objet du DataContext après l'avoir utilisé

var results = from t in DBContext.table select t where t.criteria=your select criteria 
foreach (var result in results) 
{ 
    DoSomething(result); 
    DbContext.Detach(result); 
} 

Sinon, si vous utilisez pas suivi, vous n'avez pas besoin de détacher vos objets