C# Linq通用GroupBy日期和其他字段同时显示

C# Linq通用GroupBy日期和其他字段同时显示,c#,linq,C#,Linq,我希望能够使用Linq同时按年/月/周/日以及其他属性对一些数据库结果进行分组。用户应该能够在运行时按年/月/周/日在分组之间切换。典型的情况是,我有某种DTO,它包含DateTime时间戳以及其他属性 更新:我找到了一个有效的解决方案。见下文 对于仅按日期分组(不包括按其他属性分组),我创建了一个DateGroupKey类和一个扩展方法: DateGroupKey类 public class DateGroupKey { public int Year { get; set; }

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

更新:我找到了一个有效的解决方案。见下文

对于仅按日期分组(不包括按其他属性分组),我创建了一个
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());
}
这是应该的。现在,我想进一步阐述这个想法,不仅可以按日期分组,还可以同时对其他一些领域进行分组。假设
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);
var group=results.GroupByDate(x=>x.TimeStamp,x=>x.ProductGroup,x=>x.Variant,DateGroupType.Month);
但我似乎不知道该怎么做。我考虑将
DateGroupKey
抽象为一个
IDateGroupKey
接口,该接口包含年、月、周和日属性,并以某种方式告诉GroupBy扩展如何映射到这个特定的实现,但我不确定这是否正确(或simplay'a')


有什么好主意可以解决这个问题吗?

我想说你需要这样做:

var grouped = results.GroupByDate<ProductDto>(x => x.TimeStamp, DateGroupType.Month)
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>>;
        }
    }
我的模范班

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);
            }
        }
    }
您可以创建自己的收藏,以满足您的需要。它可能看起来像这样:

var grouped = results.GroupByDate<ProductDto>(x => x.TimeStamp, DateGroupType.Month)
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>>;
        }
    }
公共类MyClass:集合
{
公共枚举组类型
{
GroupByYear=0,
GroupByMonth=1,
GroupByWeek=2
}
公共MyClass(IEnumerable类)
{
foreach(类中的var myClass)
{
添加(myClass);
}
}
public IEnumerable GroupByYear=>this.GroupBy(x=>x.Date.Year);
public IEnumerable GroupByMonth=>this.GroupBy(x=>new{x.Date.Year,x.Date.Month});
public IEnumerable GroupByWeek=>this.GroupBy(x=>new{x.Date.Year,x.Date.WeekNumber()});
公共IEnumerable GetGroupedValue(GroupType GroupType)
{
var propertyName=groupType.ToString();
var property=GetType().GetProperty(propertyName);
返回属性?.GetValue(this)作为IEnumerable;
}
}
这将根据您要求的日期要求进行分组


更新:

所以,我找到了一个有效的解决方案-我不太满意,但现在就可以了

我创建了一个通用复合键,其中包含一个包含日期分组信息的
DateGroupKey
对象,以及一个通用对象键(指向不属于日期的标识分组字段)

现在我的扩展方法如下所示

public static class Extensions
{
    // This was the original extension
    public static IEnumerable<IGrouping<DateGroupKey, TValue>> GroupByDate<TValue>(
        this IEnumerable<TValue> source, 
        Func<TValue, DateTime> dateSelector,
        DateGroupType dateGroupType)
    {
        Func<TValue, DateGroupKey> keySelector = DateKeySelector(dateSelector, dateGroupType);
        return source.GroupBy(keySelector, new DateGroupKeyComparer());
    }

    // New extension method supporting composite grouping
    public static IEnumerable<IGrouping<DateGroupCompositeKey<TKey>, TValue>> GroupByDateComposite<TKey, TValue>(
        this IEnumerable<TValue> source,
        Func<TValue, DateTime> dateSelector,
        Func<TValue, TKey> keySelector,
        IEqualityComparer<TKey> keyComparer,
        DateGroupType dateGroupType)
    {
        var dateKeySelector = DateKeySelector(dateSelector, dateGroupType);

        DateGroupCompositeKey<TKey> func(TValue val) => 
            new DateGroupCompositeKey<TKey>
            {
                DateKey = dateKeySelector(val),
                ObjectKey = keySelector(val)
            };

        return source.GroupBy(func, new DateGroupCompositeKeyComparer<TKey>(keyComparer));
    }

    private static Func<TValue, DateGroupKey> DateKeySelector<TValue>(Func<TValue, DateTime> dateSelector, DateGroupType dateGroupType)
    {
        Func<TValue, DateGroupKey> dateKeySelector = null;
        switch (dateGroupType)
        {
            case DateGroupType.Year:
                dateKeySelector = val => new DateGroupKey { Year = dateSelector(val).Year };
                break;
            case DateGroupType.Month:
                dateKeySelector = val => new DateGroupKey { Year = dateSelector(val).Year, Month = dateSelector(val).Month };
                break;
            case DateGroupType.Week:
                dateKeySelector = val => new DateGroupKey { Year = dateSelector(val).Year, Week = dateSelector(val).GetIso8601WeekOfYear() };
                break;
            case DateGroupType.Day:
                dateKeySelector = 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 dateKeySelector;
    }

}
公共静态类扩展
{
//这是最初的扩展
公共静态IEnumerable GroupByDate(
这是一个数不清的来源,
Func日期选择器,
DateGroupType(日期组类型)
{
Func keySelector=DateKeySelector(dateSelector,dateGroupType);
返回source.GroupBy(keySelector,newDateGroupKeyComparer());
}
//支持组合分组的新扩展方法
公共静态IEnumerable GroupByDateComposite(
这是一个数不清的来源,
Func日期选择器,
Func键选择器,
iQualityComparer键比较器,
DateGroupType(日期组类型)
{
var dateKeySelector=dateKeySelector(dateSelector,dateGroupType);
DateGroupCompositeKey func(TValue val)=>
新DateGroupCompositeKey
{
DateKey=dateKeySelector(val),
ObjectKey=keySelector(val)
};
返回source.GroupBy(func,新的DateGroupCompositeKeyComparer(keyComparer));
}
专用静态Func DateKeySelector(Func dateSelector,DateGroupType DateGroupType)
{
Func dateKeySelector=null;
开关(dateGroupType)
{
案例日期组类型。年份:
dateKeySelector=val=>newDateGroupKey{Year=dateSelector(val).Year};
打破
案例日期组类型。月份:
dateKeySelector=val=>new DateGroupKey{Year=dateSelector(val).Year,Month=dateSelector(val).Month};
打破
案例日期组类型。周:
dateKeySelector=val=>new DateGroupKey{Year=dateSelector(val).Year,Week=dateSelector(val).GetIso8601WeekOfYear()};
打破
案例日期组类型。日期:
dateKeySelector=val=>new DateGroupKey{Year=dateSelector(val).Year,Month=dateSelector(val).Month,Day=dateSelector(val).Day};
打破
违约:
抛出新的NotSupportedException($“组类型不受支持:{dateGroupType}”);
}
返回日期键选择器;
}
}
现在可以使用扩展名(与原始帖子一样,只按日期分组)

var group=results.GroupBy(x=>x.TimeStamp,DateGroupType.Month);
或者在复合版本中

var grouped = results.GroupByDateComposite<int, ProductDto>(
    x => x.TimeStamp,
    x => x.ProductGroup,
    EqualityComparer<int>.Default,
    DateGroupType.Month);
var group=results.GroupByDateComposite(
x=>x.TimeStamp,
x=>x.ProductGroup,
EqualityComparer.Default,
DateGroupType.Month);
据我所知,不利的一面是,对于更复杂的分组,比如

var grouped = results.GroupByDateComposite<CustomKey, ProductDto>(
    x => x.TimeStamp,
    x => new CustomKey{ ProductGroup = x.ProductGroup, Variant = x.Variant},
    new CustomKeyComparer(),
    DateGroupType.Month);
var group=results.GroupByDateComposite(
x=>x.TimeStamp,
x=>new CustomKey{ProductGroup=x.ProductGroup,Variant=x.Variant},
新的CustomKeyComparer(),
DateGroupType.Month);
我需要创建单独的关键类和对应类
var grouped = results.GroupByDateComposite<CustomKey, ProductDto>(
    x => x.TimeStamp,
    x => new CustomKey{ ProductGroup = x.ProductGroup, Variant = x.Variant},
    new CustomKeyComparer(),
    DateGroupType.Month);