You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在LINQ中无聚合转置ObservableCollection<DataValue>数据?

问题描述

我将每个采集参数的数据存储在ObservableCollection<DataValue>集合中,DataValue类定义如下:

public sealed class DataValue: BaseModel {
    private int _id;
    public int ID { get { return _id; } set { _id = value; NotifyPropertyChanged("ID"); } }
    private DateTime _timevalue;
    public DateTime TimeValue { get { return _timevalue; } set { _timevalue = value; NotifyPropertyChanged("TimeValue"); } }
    private double _value;
    public double Value { get { return _value; } set { _value = value; NotifyPropertyChanged("Value"); } }
    private string _name;
    public string Name { get { return _name; } set { _name = value; NotifyPropertyChanged("Name"); } }
}

从数据库层获取的数据格式如下:

IDDateValueName
12019-11-17 20:00:00200.05Name1
12019-11-17 20:03:00210.05Name1
12019-11-17 20:06:00225.45Name1
............
22019-11-17 20:00:001500Name2
22019-11-17 20:03:001600Name2
22019-11-17 20:06:001550Name2
............
32019-11-17 20:00:000.105Name3
32019-11-17 20:03:000.267Name3
32019-11-17 20:06:000.560Name3
............

希望通过LINQ仅执行转换操作(不使用聚合)将其转置,使每个参数Name1、Name2、Name3……NameN成为列,得到如下目标格式:

DateName1Name2Name3Name[n]
2019-11-17 20:00:00200.0515000.105xxx.xx
2019-11-17 20:03:00210.0516000.267xxx.xx
2019-11-17 20:06:00225.4515500.560xxx.xx
...............

请问该如何实现这一需求?


解决方案

这是个典型的数据转置(透视)需求,刚好可以用LINQ的分组+映射来实现——完全不需要聚合操作,符合你的要求。根据你是否提前知道参数名称,有几种不同的实现方式:

1. 已知参数名称时:用匿名类型直接构造

如果你的参数列表是固定的(比如确定只有Name1、Name2、Name3),直接按时间分组后,在每个分组里提取对应名称的值就行:

var pivotedResult = yourDataCollection
    .GroupBy(dv => dv.TimeValue) // 按时间戳分组,每个组对应一行结果
    .Select(group => new {
        Date = group.Key,
        Name1 = group.FirstOrDefault(dv => dv.Name == "Name1")?.Value ?? 0.0,
        Name2 = group.FirstOrDefault(dv => dv.Name == "Name2")?.Value ?? 0.0,
        Name3 = group.FirstOrDefault(dv => dv.Name == "Name3")?.Value ?? 0.0
        // 有更多固定参数就继续加对应的行
    });

这里用FirstOrDefault是为了避免某个时间点缺失某参数数据时抛出异常,默认值(这里写的0.0)你可以根据业务调整成null或者其他值。

2. 参数名称动态时:用字典存储

如果参数是动态生成的(比如可能随时新增NameN),强类型就不太灵活了,这时用字典来存每行的键值对最合适:

// 先获取所有唯一的参数名称
var allParameterNames = yourDataCollection
    .Select(dv => dv.Name)
    .Distinct()
    .ToList();

var pivotedResult = yourDataCollection
    .GroupBy(dv => dv.TimeValue)
    .Select(group => {
        var rowDict = new Dictionary<string, object>
        {
            ["Date"] = group.Key // 先把时间列放进去
        };
        // 遍历所有参数名称,填充对应的值
        foreach (var name in allParameterNames)
        {
            rowDict[name] = group.FirstOrDefault(dv => dv.Name == name)?.Value ?? 0.0;
        }
        return rowDict;
    });

这种方式不管参数数量怎么变都能自动适配,缺点是后续取值需要通过键名,没有强类型的编译提示。

3. 想要动态列+类属性体验:用ExpandoObject

如果既想要动态列的灵活性,又想像访问类属性一样取值,可以用ExpandoObject:

var allParameterNames = yourDataCollection
    .Select(dv => dv.Name)
    .Distinct()
    .ToList();

var pivotedResult = yourDataCollection
    .GroupBy(dv => dv.TimeValue)
    .Select(group => {
        dynamic dynamicRow = new ExpandoObject();
        dynamicRow.Date = group.Key;
        // 把ExpandoObject转成字典来添加动态属性
        var rowDict = (IDictionary<string, object>)dynamicRow;
        foreach (var name in allParameterNames)
        {
            rowDict[name] = group.FirstOrDefault(dv => dv.Name == name)?.Value ?? 0.0;
        }
        return dynamicRow;
    });

之后你就可以像dynamicRow.Date、dynamicRow.Name1这样访问属性了,不过要注意动态类型没有编译时检查,使用时要小心空值或者不存在的属性。

这几种方式都是纯LINQ转换,没有用Sum、Avg这类聚合操作,完全满足你的要求。


内容的提问来源于stack exchange,提问作者amaranth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:51