高级LINQ分组和投影查询用于分层数据(EF 4.0 + LINQ + ASP.NET MVC + HighCharts)

4

问题概述:

我有数据库表,描述了使用Web服务从多个设备中收集数据并存储到集中式数据库中的情况。

为了提高性能(更快的查询和多索引分组),存储结果的表被故意去规范化。 我正在使用Entity Framework和Linq进行数据访问。

我需要适当地 设计Linq查询,实现层次分组和投影


设备数据库建模概述:

目前我有两种类型的设备

1. Rfid 设备

第一个表是 RfidTag,它描述了收集数据的标签,1 个 RfidTag = 1 个传感器。例如,1 个标签可以获取关于温度的数据。

第二个表是 RfidReader,它描述了读取模块,它会收集并发送所有附加标签的数据。单个 RfidReader 上可以连接多个 RfidTag,然而在读取时期,每个 RfidTag 只能连接到一个 RfidReader。

CREATE TABLE [dbo].[RfidTag]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime DATETIME NOT NULL DEFAULT GETDATE(),
    ModifiedDateTime DATETIME,
    --
    ReaderId INT NULL,                  -- Reference to reader
    SensorTypeId INT NOT NULL,          -- Reference to sensor type
    SensorParameters NVARCHAR(50) NULL, -- Sensor parameters
    Hex  NVARCHAR(50) NOT NULL,         -- Hex tag identifier stored as string
    Name NVARCHAR(50) NOT NULL,         -- Tag name
    [Description] NVARCHAR(200) NULL,   -- Tag description
    --
)

CREATE TABLE [dbo].[RfidReader]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime  DATETIME NOT NULL DEFAULT GETDATE(),
    ModifiedDateTime DATETIME,
    --
    Name        NVARCHAR(20),        -- Tag name
    [Description]   NVARCHAR(200),   -- Tag description                                             
    SerialNumber    NVARCHAR(12),    -- Unique device serial name               
    --
)

每个RFID读卡器都可以附加到特定的测量区域,该区域是为特定的结构描述的。
CREATE TABLE [dbo].[RfidReaderPlacement]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime DATETIME NOT NULL DEFAULT GETDATE(),
    --
    ReaderId INT NOT NULL,              -- Reference to Reader.
    MeasurementZoneId INT NOT NULL,     -- Reference to Measurement Zone.
    StartDate DATETIME NOT NULL,        -- Start date of reading.
    StopDate  DATETIME,                 -- End date of reading.
    --
)

RfidTag 收集的单个数据保存在非规范化表中。该表存储数百万条记录,并且负载非常重。我们将使用 LINQ 查询从这个表中收集数据。

CREATE TABLE [dbo].[RfidReading]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime DATETIME NOT NULL DEFAULT GETDATE(),
    ModifiedDateTime DATETIME,
    --
    ReaderPlacementId INT NOT NULL, -- Reference to Rfid Reader Placement
    ConstructionId  INT NOT NULL,   -- Reference to Construction
    MeasurementZoneId INT NOT NULL, -- Reference to Measurement Zone
    ReaderId INT NOT NULL,          -- Reference to Rfid Reader
    TagId    INT NOT NULL,          -- Reference to Rfid Tag
    SensorTypeId INT NOT NULL,      -- Reference to Sensor Type
    ReadingDate DATETIME NOT NULL,  -- Reading date
    Value FLOAT NOT NULL            -- Measured value
    --
)

2. ZigBee设备

第一个表是ZigBeeNodeProbe,描述了收集数据的单个探针,1个单独的ZigBeeNodeProbe = 1个单独的传感器。例如,一个单独的探针可以获取温度数据。

第二个表是ZigBeeNode,描述了包含附加探针的单个设备,1个单独的ZigBeeNode = 3个ZigBeeNodeProbes

第三个表是ZigBeeReader,描述了读取模块,它从连接的节点中收集并发送所有数据。单个ZigBeeReader上连接的ZigBeeNodes(具有连接的ZigBeeNodeProbes)的数量没有限制。然而,在读取期间,一个单独的ZigBeeNode只能连接到一个单独的ZigBeeReader。

CREATE TABLE [dbo].[ZigBeeNodeProbe]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime DATETIME NOT NULL DEFAULT GETDATE(),
    --
    NodeId INT NULL,                    -- Reference to node
    SensorTypeId INT NOT NULL,          -- Reference to sensor type
    SensorParameters NVARCHAR(50) NULL, -- Sensor parameters
    SocketNumber  INT NOT NULL,         -- Socket number used in parent ZigBeeNode
    Name NVARCHAR(50) NOT NULL,         -- Node name
    [Description] NVARCHAR(200) NULL,   -- Node description
    --
)

CREATE TABLE [dbo].[ZigBeeNode]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime DATETIME NOT NULL DEFAULT GETDATE(),
    ModifiedDateTime DATETIME,
    --
    ReaderId INT NULL,                    -- Reference to reader
    NetworkAddress  NVARCHAR(50) NOT NULL,-- Node address in ZigBee network
    Name NVARCHAR(50) NOT NULL,           -- Given name
    [Description] NVARCHAR(200) NULL,     -- Tag description
    SocketCount INT NOT NULL DEFAULT 0,   -- Count of available sockets to plug in probe
    NodeFrequency INT NULL,               -- Node frequency
)

CREATE TABLE [dbo].[ZigBeeReader]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime  DATETIME NOT NULL DEFAULT GETDATE(),
    ModifiedDateTime DATETIME,
    --
    Name        NVARCHAR(20),        -- Tag name
    [Description]   NVARCHAR(200),   -- Tag description                                             
    SerialNumber    NVARCHAR(12),    -- Unique device serial name               
    --
)

每个ZigBeeReader可以附加到特定的测量区域,该区域是为特定的构造描述的。
CREATE TABLE [dbo].[ZigBeeReaderPlacement]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime DATETIME NOT NULL DEFAULT GETDATE(),
    --
    ReaderId INT NOT NULL,              -- Reference to Reader.
    MeasurementZoneId INT NOT NULL,     -- Reference to Measurement Zone.
    StartDate DATETIME NOT NULL,        -- Start date of reading.
    StopDate  DATETIME,                 -- End date of reading.
    --
)

ZigBeeNodeProbe收集的单个数据保存在非规范化表中。该表存储数百万条记录,负载非常高。我们将使用LINQ查询从该表中获取数据。

CREATE TABLE [dbo].[ZigBeeReading]
(
    Id INT IDENTITY(1,1),
    CreatedDateTime DATETIME NOT NULL DEFAULT GETDATE(),
    ModifiedDateTime DATETIME,
    --
    ReaderPlacementId INT NOT NULL, -- Reference to ZigBee Reader Placement
    ConstructionId    INT NOT NULL, -- Reference to Construction
    MeasurementZoneId INT NOT NULL, -- Reference to Measurement Zone
    ReaderId      INT NOT NULL,     -- Reference to ZigBee Reader
    NodeId        INT NOT NULL,     -- Reference to ZigBee Node
    ProbeId       INT NOT NULL,     -- Reference to ZigBee Node Probe
    SensorTypeId      INT NOT NULL, -- Reference to Sensor Type
    ReadingDate   DATETIME NOT NULL,-- Reading date
    Value         FLOAT NOT NULL    -- Measured value
    --
)

查询、分组和投影问题:

正如你在上面看到的,我们有两个包含由两种设备收集的数据的分散表。 是的,我们可以假设 RfidTag 在业务建模上与 ZigBeeNodeProbe 几乎相同。

RfidReader
-- RFidTag
-- RFidTag
...

ZigBeeReader
-- ZigBeeNode
---- ZigBeeNodeProbe
---- ZigBeeNodeProbe
---- ZigBeeNodeProbe
-- ZigBeeNode
---- ZigBeeNodeProbe
---- ZigBeeNodeProbe
---- ZigBeeNodeProbe
...

现在我们需要查询这两个表,从不同的表中投影值到相同的视图模型中,并添加特定的分组以过滤数据。
常见情况:
我们想创建一个图表,展示特定测量区域的平均温度。请记住,特定的测量区域可能包含多个附加读取器(包括Rfid和ZigBee),因此我们必须提供一系列数据。
我正在使用HightStock http://www.highcharts.com/products/highstock 图表,必须启用以下功能:
- 缩放为1个月、3个月、6个月等。 - 日期范围从开始到结束。 - 导出功能。 - 启用和禁用系列的图例。
图表示例:

http://jsfiddle.net/hNHUY/1/

问题:

如何为分层数据创建LINQ分组和投影查询? 我们需要为RfidReading和ZigBeeReading表提供共同的视图模型。

我的第一次尝试是类似于:

public class ReadingReaderDataModel
{
    public string SeriesName { get; set; }
    public ReadingPeriod ReadingPeriod { get; set; }

    public IEnumerable<ReadingNodeDataModel> ReadingNodeDataModels { get; set; }
    public ReadingReaderDataModel()
    {
        ReadingNodeDataModels = new List<ReadingNodeDataModel>();
    }
}

public class ReadingNodeDataModel
{
    public string NodeName { get; set; }
    public IEnumerable<double> DataValues { get; set; }
    public ReadingNodeDataModel()
    {
        DataValues = new LinkedList<double>();
    }
}

public enum ReadingPeriod
{
    OneMonth, ThreeMonths, SixMonths, YearToDay, OneYear, All
}

public enum ReaderType
{
    Rfid, ZigBee
}

稍后我需要为ASP.NET MVC控制器设计LINQ投影,但是我不知道如何创建包含按OneMonth、ThreeMonths、SixMonths、YearToDay、OneYear和All分组的时间段平均值的正确查询。 有人能帮助我设计这个高级LINQ查询吗?
编辑
请不要给我任何大型数据集的“建议”……
这不是关于性能的问题。
这个问题涉及复杂的LINQ查询。 我正在寻找适当的LINQ代码,而不是像“那辆车更快,你应该尝试使用那辆车”这样的答案……请提供代码答案……如果您甚至不尝试理解场景并提供任何代码,请不要参与……
我特别添加了赏金以找到LINQ的解决方案,因为这是一个高级LINQ问题,就像这个问题的主题一样。
1个回答

7
我了解到,您的数据库中有数百万条记录,需要应用程序性能非常好,而且结构非常复杂。
远离 EF(Entity Framework),我是认真的。对于任何需要从大型查询获取出色性能的内容,请勿使用EF,您可以在较小的数据集上使用它。
我的建议是手动编写查询,因为如果您想尽可能地优化EF,那么实际上您将无法跳过太多代码,并且基本上将以LINQ方式编写SQL语句。
如果您想更具体些,可以使用Dapper。它是运行StackOverflow的“ORM”。这里有速度比较:http://code.google.com/p/dapper-dot-net/ 但是,如果您一定要使用EF...
不要尝试编写一个查询。 不会发生的。相反,将它们分开。为每组需要进行操作的查询编写不同的查询。这是最简单的方法。我相信您应该已经有一个用于仅获取一组数据的LINQ查询。现在将其与您所需的每个其他组重复,特别是如果其中包含大量Wheres和OrderBys。
来源:每天处理大型数据集查询的人。

我在编写一些针对大型数据库的复杂查询时也有类似的经历,最终放弃了使用EF(至少在处理复杂任务时是这样,但我仍然能够将其用于一些简单的任务,例如管理等)。 - Tom Chantler
@Dommer 我现在不是在寻求性能优化,而是在寻找适合(高级)LINQ查询的方案。 - Daniel Skowroński
@Baboon,“你打算如何在C#中加载所有这些数据,即内存中?数百万行?计算机只能放置有限的RAM。” - 我从未说过我会返回数百万条记录...适当的LINQ将在SQL Server侧计算并创建LINQ分组,并返回带有DateTime边界的计算值到Web应用程序...所有这些都将在SQL服务器端进行,并稍后使用适当的LINQ投影在Web应用程序上缓存为简单模型...各位,我正在寻找熟悉分组和投影知识的LINQ大师,而不是推荐其他方法的建议... - Daniel Skowroński
我的观点是:如果你不打算将它加载到内存中,那么EF只会妨碍你。在ORM中,M代表MAPPER,而你不想要映射。NeroS给了你比LINQ大师更好的答案。 - Louis Kottmann
3
狒狒说的没错。做你想做的事情并不是很难(至少不是那么难),但事实是,你只是不想去做那件事。你认为你想做,但其实你并不想。这些深入和庞大的东西从来就不打算与完整的ORM一起使用。当然,使用一个简单的微型ORM将获取的结果转换为POCOs没有任何问题,但最终你还是要手写它。相信我(以及这些评论中的其他人)告诉你这样做是正确的。 - NeroS
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接