天气记录器






4.83/5 (5投票s)
下载XML文件并将其内容插入数据库的自动化过程。
引言
本文介绍了下载XML文件并将其插入数据库的过程,以及使用两种不同方法(即SQL Server作业和Windows任务计划程序)自动化此任务。
背景
国家海洋和大气管理局 (NOAA) 提供了广泛的公共网络服务,可以通过编程方式访问。具体来说,这个链接 [1] 引起了我的注意。NOAA 提供对美国约1800个地点(主要按机场站ID组织)的观测天气条件的访问。这些地点有各种天气传感器记录气压、风速、湿度、温度等值。这些观测数据被打包成一个XML文件,主要用于机器到机器的传输 [2]。每小时都会为每个观测站生成RSS和XML文件。建议的获取时间是每小时15分钟后。这个场景提供了一个机会来自动化下载每小时更新的新XML文件的任务。下一节将介绍应用程序的概述。
概述
该过程分为四个步骤,如下图1所示。
在我深入实施细节之前,我想讨论一下应用程序设计所基于的边界。NOAA 生成的XML文件约为~2.25 KB。通常每天应该有24个新文件,因为文件每小时更新一次,但我观察到文件有时几个小时都不会改变。请查看此链接 [3] 以查看应用程序使用的XML文件,我对以下节点感兴趣:ObservationTime、Temp_F、Temp_C、Relative Humidity、Wind MPH、pressure_mb、dewpoint_F。
主要逻辑嵌入在WeatherLogger.exe代码中,这是第二步。这是一个基于控制台的 C# 应用程序,使用 Visual Studio 2012 和 .NET 4.5 框架开发。在下一节中,我将介绍所采用的设计策略。 从一开始,我就想将 XML 文件中的值存储到数据库的表中。对于第三步,我编写了两个存储过程,并在 SQL Server 2012 中创建了两个名为“Observations”和“AppErrors”的表。 对于最后一部分,即第四步,自动化通过两种方式实现。我使用了 Windows 任务计划程序和 SQL Server 作业来每小时运行 WeatherLogger.exe 以获取新文件。与 Windows 服务应用程序相比,这两种方法都很好且更好。
源代码在GitHub上,请查看此链接 [4] 随意拉取代码,尝试一下,我欢迎对代码的批评。如果有改进空间,我想知道。下一节将介绍第二步的实现细节。
天气记录器
这是我最初的方法,后来我放弃了。 大部分工作由控制台应用程序完成,数据库部分很轻量。我曾考虑定期轮询 NOAA 服务器,下载 XML 文件,解析它,移除未使用的节点,并将所需的节点插入表中。我能够实现所有这些,但棘手的部分是定期轮询服务器。我通过 Last-Modified 和 If-Last-Modified http 头 [5] 努力解决。调整了一个在建议获取时间运行的定时器。尽管建议的获取时间是每小时15分钟后,但获取时间并不可靠。这扰乱了我的定时器,然后我选择了第二个选项。
对于第二个选项,我决定将数据库操作密集化,并保持控制台应用程序轻量化。我听说了很多关于 SQL Server 中 XML 数据类型和 MERGE 语句的好处,并决定使用它们。这个选择显著减少了代码大小,并且解决方案变得优雅。主要逻辑捆绑在一个名为 CheckAndInsert 的存储过程中,该存储过程接受 @xml
文件作为参数。
想法是将XML/URI转换为C#字符串,并通过将该字符串作为参数调用存储过程。所需节点的值被插入到一个#staging表中,该表与保存观测数据的主表相同。
select
current_observation.value('(observation_time)[1]',
'varchar(50)') as ObsTime,
current_observation.value('(temp_f)[1]', 'float') as
Temp_f,
current_observation.value('(temp_c)[1]', 'float') as
Temp_c,
current_observation.value('(relative_humidity)[1]', 'float')
as RelHum,
current_observation.value('(wind_mph)[1]', 'float') as
Wind_mph ,
current_observation.value('(pressure_mb)[1]', 'float')
as Pressure_mb,
current_observation.value('(dewpoint_f)[1]', 'float')
as Dewpt_f
into #Staging
from
@Raw_Xml.nodes('current_observation') as rawdata(current_observation)
然后使用 merge tsql 命令,选择 Observations 作为目标表,#Staging 作为源表。比较是基于观测时间进行的,这是最可靠的列。如果不匹配,则将 XML 文件中的数据插入数据库。最后,#Staging 表被删除。
;merge into Observations
using #Staging
on #Staging.ObsTime = Observations.ObsTime
when not matched then
insert (ObsTime,Temp_f,Temp_c,RelHum,Wind_mph,Pressure_mb,Dewpt_f)
values
(ObsTime,Temp_f,Temp_c,RelHum,Wind_mph,Pressure_mb,Dewpt_f);
这种方法是可扩展的,并且适用于这种场景,因为 XML 文件的大小非常小,merge 语句的执行时间非常快。无需存储或解析 XML 文件。这个存储过程在 InsertIntoDb
类中的 InsertData
方法中被调用。控制台应用程序的类图如图2所示。
应用程序中有三个主要类:DownloadXML
、InserIntoDb
和 LogErrors
,Program
类负责协调应用程序的流程,并且是入口点。SQL Server 中的 XML 数据类型非常强大,此数据类型将 XML 文件读取为 blob,在 C# 中,它应该是一个格式良好的 XML 文件,存储为简单的 C# 字符串。
DownloadXML
中的 DownloadWeatherXML
方法使用 WebClient
类将请求的 URL 下载为字符串。
string xml = new WebClient().DownloadString(_url);
string modifiedxml = xml.Replace(" \r\n", "");
如果我直接传递这个字符串而不替换编码,那么我将从 sp_CheckAndInsert 存储过程中得到以下错误。我无法解决它,但如果我替换编码,它就能工作。
“XMLparsing: line 1, character 43, unable to switch the encoding”
LogErrors
是一个通用的辅助类,它有一个InsertErrors()
方法。这个方法用于记录代码中任何catch块捕获到的任何错误。数据由InsertIntoAppErros
存储过程插入。在阅读了《Joel on Software》[6]一书后,我被迫使用这个类。
自动化
WeatherLogger.exe 必须每小时运行一次,这需要自动化。我想同时使用 SQL Server Job 和 Windows 任务计划程序。我截取了如何设置作业的屏幕截图,请查看下面的链接,并已上传到 flickr
调度 SQL Server 作业
结果
我连续两天运行了调度作业和任务约两个小时,下面是观测表的截图。
结论
总的来说,我个人认为这种方法是可扩展的,执行 .exe 的选择可以委托给系统管理员或数据库管理员。我曾在 C# 中解析 XML,但 SQL Server 提供的 merge 语句和 XML 数据类型的简便性是无与伦比的。 未来,我希望建立一个 SSRS Web 服务,并使用观测数据为客户端呈现报告。此外,我还希望将整个过程实现为 SQL CLR 存储过程。
参考文献
[1] http://w1.weather.gov/xml/current_obs/
[2] http://products.weather.gov/PDD/NWS_Current_Observations_RSS_XML.pdf
[3] http://w1.weather.gov/xml/current_obs/KEWR.xml
[4] https://github.com/tkmallik/WeatherLogger.git
[5] http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.ifmodifiedsince.aspx
[6] http://www.amazon.com/Joel-Software-Occasionally-Developers-Designers/dp/1590593898
[7] http://www.flickr.com/photos/90662753@N08/sets/72157632124494853/
[8] http://www.flickr.com/photos/90662753@N08/sets/72157632128725180/