39 votes

Groupe par Semaines dans LINQ to entities

J'ai une application qui permet aux utilisateurs d'entrer le temps qu'ils consacrent à leur travail, et je suis en train de faire quelques bons rapports construit pour ce qui tire profit de LINQ to entities. Parce que chaque TrackedTime a TargetDate qui est juste la "Date" partie d'un DateTime, il est relativement simple de groupe à la fois par l'utilisateur et la date (je pars le "où" clauses pour des raisons de simplicité):

var userTimes = from t in context.TrackedTimes
                group t by new {t.User.UserName, t.TargetDate} into ut
                select new
                {
                    UserName = ut.Key.UserName,
                    TargetDate = ut.Key.TargetDate,
                    Minutes = ut.Sum(t => t.Minutes)
                };

Grâce à l' DateTime.Month de la propriété, le regroupement par utilisateur et par Mois est seulement un peu plus compliqué:

var userTimes = from t in context.TrackedTimes
                group t by new {t.User.UserName, t.TargetDate.Month} into ut
                select new
                {
                    UserName = ut.Key.UserName,
                    MonthNumber = ut.Key.Month,
                    Minutes = ut.Sum(t => t.Minutes)
                };

Maintenant vient la partie difficile. Est-il un moyen fiable de groupe par Semaine? J'ai essayé les options suivantes en fonction de cette réponse à une semblable LINQ to SQL question:

DateTime firstDay = GetFirstDayOfFirstWeekOfYear();
var userTimes = from t in context.TrackedTimes
                group t by new {t.User.UserName, WeekNumber = (t.TargetDate - firstDay).Days / 7} into ut
                select new
                {
                    UserName = ut.Key.UserName,
                    WeekNumber = ut.Key.WeekNumber,
                    Minutes = ut.Sum(t => t.Minutes)
                };

Mais LINQ to entities n'apparaît pas à l'appui des opérations arithmétiques sur les objets DateTime, de sorte qu'il ne sait pas comment faire, (t.TargetDate - firstDay).Days / 7.

J'ai réfléchi à la création d'un Affichage dans la base de données que tout simplement les cartes de quelques jours à quelques semaines, et puis en ajoutant que la Vue de mon Entity Framework contexte et de se joindre à elle dans ma requête LINQ, mais cela ressemble à beaucoup de travail pour quelque chose comme ça. Est-il un bon travail autour de l'arithmétique approche? Quelque chose qui fonctionne avec Linq to entities, que je peux tout simplement l'intégrer dans le LINQ déclaration sans avoir à toucher la base de données? D'une certaine façon de dire Linq to entities comment soustraire une date à partir d'un autre?

Résumé

Je tiens à remercier tout le monde pour leur réfléchie et créative réponses. Après tout ce va-et-vient, c'est comme la vraie réponse à cette question est "attendre jusqu'à ce que .NET 4.0." Je vais donner la prime à Noldorin pour donner le plus pratique, qui exploite encore LINQ, avec une mention spéciale à Jacob Proffitt de venir avec une réponse qui utilise Entity Framework, sans la nécessité pour les modifications de la base de données secondaires. Il y avait d'autres réponses grands, trop, et si vous êtes à la recherche à la question pour la première fois, je voudrais vous recommandons fortement de lire par tous ceux qui ont été voté, ainsi que leurs commentaires. Cela a été vraiment un superbe exemple de la puissance de StackOverflow. Merci à vous tous!

40voto

Noldorin Points 67794

Vous devriez être en mesure de forcer la requête à utiliser LINQ to Objects, plutôt que de LINQ to entities pour le groupement, à l'aide d'un appel à l' AsEnumerable méthode d'extension.

Essayez les solutions suivantes:

DateTime firstDay = GetFirstDayOfFirstWeekOfYear();
var userTimes = 
    from t in context.TrackedTimes.Where(myPredicateHere).AsEnumerable()
    group t by new {t.User.UserName, WeekNumber = (t.TargetDate - firstDay).Days / 7} into ut
    select new
    {
        UserName = ut.Key.UserName,
        WeekNumber = ut.Key.WeekNumber,
        Minutes = ut.Sum(t => t.Minutes)
    };

Ce serait au moins dire que l' where clause est exécuté par LINQ to entities, mais l' group clause, qui est trop complexe pour les Entités à gérer, se fait par LINQ to Objects.

Laissez-moi savoir si vous avez un peu de chance avec ça.

Mise à jour

Voici une autre suggestion, qui pourrait vous permettre d'utiliser LINQ to entities pour l'ensemble de la chose.

(t.TargetDate.Days - firstDay.Days) / 7

Il suffit de se développe l'exploitation, de sorte que seuls entier soustraction est effectuée plutôt que d' DateTime de la soustraction.

Il est actuellement testées, de sorte qu'il peut ou peut ne pas fonctionner...

8voto

HBlackorby Points 312

Vous pouvez utiliser SQL aussi des fonctions dans le LINQ aussi longtemps que vous utilisez sql server:

using System.Data.Objects.SqlClient;

var codes = (from c in _twsEntities.PromoHistory
                         where (c.UserId == userId)
                         group c by SqlFunctions.DatePart("wk", c.DateAdded) into g
                         let MaxDate = g.Max(c => SqlFunctions.DatePart("wk",c.DateAdded))
                         let Count = g.Count()
                         orderby MaxDate
                         select new { g.Key, MaxDate, Count }).ToList();

Cet échantillon a été adapté à partir de MVP Zeeshan Hirani dans ce thread: un MSDN

7voto

Sam Mackrill Points 1082

J'ai eu exactement ce problème, dans mon Temps, le suivi de l'application qui en a besoin pour des tâches de rapport par semaine. J'ai essayé de l'arithmétique approche, mais a échoué à obtenir quoi que ce soit raisonnable de travail. J'ai fini juste l'ajout d'une nouvelle colonne à la base de données avec le numéro de la semaine dans l'informatique et le calcul de ce pour chaque nouvelle ligne. Soudain, la douleur a disparu. J'ai détesté le faire puisque je suis un grand croyant dans le SEC , mais j'avais besoin de faire des progrès. Pas la réponse que vous voulez, mais d'un autre point de vue des utilisateurs. Je vais regarder cela pour une vie décente réponse.

2voto

Colin Points 9465

Bizarre personne n'a posté le GetWeekOfYear méthode de l' .NET Framework encore.

il suffit de faire votre première sélectionner, ajouter un mannequin weeknumber de la propriété, puis une boucle sur chacun d'eux, et d'utiliser cette méthode pour définir le bon weeknumber sur votre type, il devrait être assez rapide à faire jhust une boucle entre 2 sélectionne n'est-ce pas?:

public static int GetISOWeek(DateTime day)
{
  return System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(day, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
}

2voto

Jacob Proffitt Points 8187

Bon d'accord, c'est possible, mais c'est un immense PITA et qu'il contourne le LINQ partie de l'Entity Framework. Il a fallu un peu de recherche, mais vous pouvez accomplir ce que vous voulez avec la SqlServer fournisseur de fonction spécifique à sql server.DateDiff() et "Entité" SQL". Cela signifie que vous êtes de retour à la chaîne de requêtes, et d'alimenter manuellement des objets personnalisés, mais il fait ce que vous voulez. Vous pourriez être en mesure de peaufiner un peu tout ça à l'aide d'un Select à la place d'une boucle foreach et tels, mais j'ai vraiment eu que cela fonctionne sur un concepteur généré entité de YourEntities.

void getTimes()
{
    YourEntities context = new YourEntities();
    DateTime firstDay = GetFirstDayOfFirstWeekOfYear();
    var weeks = context.CreateQuery<System.Data.Common.DbDataRecord>(
        "SELECT t.User.UserName, SqlServer.DateDiff('DAY', @beginDate, t.TargetDate)/7 AS WeekNumber " +
        "FROM YourEntities.TrackedTimes AS t " +
        "GROUP BY SqlServer.DateDiff('DAY', @beginDate, t.TargetDate)/7, t.User.UserName", new System.Data.Objects.ObjectParameter("beginDate", firstDay));
    List<customTimes> times = new List<customTimes>();
    foreach (System.Data.Common.DbDataRecord rec in weeks)
    {
        customTimes time = new customTimes()
        {
            UserName = rec.GetString(0),
            WeekNumber = rec.GetInt32(1)
        };
        times.Add(time);
    }
}

class customTimes
{
    public string UserName{ get; set; }
    public int WeekNumber { get; set; }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X