首页 > 解决方案 > Linq通用GroupBy日期和其他字段同时

问题描述

我希望能够使用 Linq 同时按年/月/周/日以及其他属性对一些数据库结果进行分组。用户应该能够在运行时按年/月/周/日在分组之间切换。典型的情况是我有某种包含DateTime时间戳以及其他属性的 DTO。

更新:我找到了一个可行的解决方案。请参阅下面的帖子。

对于仅按日期分组(不包括按其他属性分组),我创建了一个DateGroupKey类和一个扩展方法:

DateGroupKey 类

public class DateGroupKey
{
    public int Year { get; set; }
    public int Month { get; set; }
    public int Week { get; set; }
    public int Day { get; set; }
}

扩展方法

public static IEnumerable<IGrouping<DateGroupKey, TValue>> GroupByDate<TValue>(
        this IEnumerable<TValue> source,
        Func<TValue, DateTime> dateSelector,
        DateGroupType dateGroupType)
{
    Func<TValue, DateGroupKey> keySelector = null;
    switch (dateGroupType)
    {
            case DateGroupType.Year:
                keySelector = val => new DateGroupKey { Year = dateSelector(val).Year };
                break;
            case DateGroupType.Month:
                keySelector = val => new DateGroupKey { Year = dateSelector(val).Year, Month = dateSelector(val).Month };
                break;
            case DateGroupType.Week:
                keySelector = val => new DateGroupKey { Year = dateSelector(val).Year, Week = dateSelector(val).GetIso8601WeekOfYear() };
                break;
            case DateGroupType.Day:
                keySelector = val => new DateGroupKey { Year = dateSelector(val).Year, Month = dateSelector(val).Month, Day = dateSelector(val).Day };
                break;
            default:
                throw new NotSupportedException($"Group type not supported: {dateGroupType}");
    }

    return source.GroupBy(keySelector, new DateGroupKeyComparer());
}

其中DateGroupKeyComparer是一个实现 的类IEqualityComparer<DateGroupKey>。我可以像这样使用扩展方法:

var grouped = results.GroupByDate<ProductDto>(x => x.TimeStamp, DateGroupType.Month)

可以正常工作。现在,我想扩展这个想法,不仅可以按日期分组,还可以同时按其他一些字段分组。假设ProductDto该类具有以下签名

public class ProductDto
{
    public DateTime TimeStamp {get; set;}
    public int ProductGroup {get; set;}
    public int Variant {get; set;}
    public double Value {get; set;}
}

我想做一些类似的事情

var grouped = results.GroupByDate<ProductDto>(x => x.TimeStamp, x => x.ProductGroup, x => x.Variant, DateGroupType.Month);

但我似乎无法解决如何解决这个问题。我考虑过将其抽象DateGroupKey为一个IDateGroupKey包含 Year、Month、Week 和 Day 属性的接口,并以某种方式告诉 GroupBy 扩展如何映射到这个特定的实现,但我不确定它是否正确(或 simplay 'a')要走的路。

关于如何解决这个问题的任何好主意?

标签: c#linq

解决方案


我会说你需要做这样的事情:

我的模特班

public class MyClass
{
    public MyClass(DateTime date, string name, int partNumber)
    {
        Date = date;
        Name = name;
        PartNumber = partNumber;
    }
    public DateTime Date { get;}

    public string Name { get;  }

    public int PartNumber {get;}
}

获取周数的扩展方法:

public static class DateTimeExtensions
{
    public static int WeekNumber(this DateTime date)
    {
        var cal = new GregorianCalendar();
        var week = cal.GetWeekOfYear(date, CalendarWeekRule.FirstDay, DayOfWeek.Sunday);
        return week;
    }
}

然后分组。

            var dataBase = new[]{
            new MyClass(new DateTime(2018,1,1),"Jane",10),
            new MyClass(new DateTime(2017,1,1),"Jane",1),
            new MyClass(new DateTime(2016,1,1),"Jane",10),
            new MyClass(new DateTime(2018,2,1),"Jane",1),
            new MyClass(new DateTime(2018,2,2),"Jane",10),
            new MyClass(new DateTime(2017,2,2),"Jane",1),
            new MyClass(new DateTime(2016,2,2),"Jane",10),
            new MyClass(new DateTime(2018,2,8),"Jane",1),
            new MyClass(new DateTime(2017,2,8),"Jane",10),
            new MyClass(new DateTime(2016,2,8),"Jane",1),
            new MyClass(new DateTime(2018,3,1),"Jane",10),
            new MyClass(new DateTime(2017,3,1),"Jane",1),
            new MyClass(new DateTime(2016,3,1),"Jane",10),
        };

        var myCollection = new MyClasses(dataBase);

                    while (true)
        {
            Console.WriteLine("Group By What");
            var type = Console.ReadLine();

            var enumType = (MyClasses.GroupType) Enum.Parse(typeof(MyClasses.GroupType), $"GroupBy{type.UppercaseFirst()}");

            foreach (var g in myCollection.GetGroupedValue(enumType))
            {
                Console.WriteLine(g.Key);
            }
        }
    }

您可以创建自己的收藏集来满足您的需求。它可能看起来像这样:

public class MyClasses : Collection<MyClass>
    {
        public enum GroupType
        {
            GroupByYear = 0,
            GroupByMonth = 1,
            GroupByWeek = 2
        }
        public MyClasses(IEnumerable<MyClass> classes)
        {
            foreach (var myClass in classes)
            {
                Add(myClass);
            }
        }

        public IEnumerable<IGrouping<object, MyClass>> GroupByYear => this.GroupBy(x => x.Date.Year);

        public IEnumerable<IGrouping<object, MyClass>> GroupByMonth => this.GroupBy(x => new { x.Date.Year, x.Date.Month});

        public IEnumerable<IGrouping<object, MyClass>> GroupByWeek => this.GroupBy(x => new { x.Date.Year, x.Date.WeekNumber()});

        public IEnumerable<IGrouping<object, MyClass>> GetGroupedValue(GroupType groupType)
        {
            var propertyName = groupType.ToString();
            var property = GetType().GetProperty(propertyName);
            return property?.GetValue(this) as IEnumerable<IGrouping<object, MyClass>>;
        }
    }

这将按您要求的日期要求分组。

更新:


推荐阅读