SQLXAgent - SQL Express 的作业 - 第 2 部分(共 6 部分)
创建和运行类似 SQL Server Enterprise 的作业 - 架构和设计决策。
第 1 部分 - 使用 SQLXAgent
第 2 部分 - 架构和设计决策 (本文)
第 3 部分 - CSV 和 Excel 导入器代码
第 4 部分 - 作业调度代码
第 5 部分 - 包如何运行
第 6 部分 - 有趣的编码
引言
本文是关于 SQLXAgent 工具的一系列文章中的第二篇,专门讨论设计和架构方面的考虑。不会有很多屏幕截图或代码片段——主要是大量的枯燥叙述。如果您不关心本文部分的主题,请随时忽略它,去做其他事情。具体的编码领域和挑战将在本文系列的第 3 部分到第 6 部分中讨论。
重要提示!!! 所有组件(包除外)都必须放在同一个文件夹中。原因是它们都共享 SQLXAgent.exe.config
文件。
假设
本文是为有 SQL Server、WPF 和 C# 工作知识的经验丰富的程序员编写的。如果您有不明白的地方,请自行谷歌搜索。
作为目标受众,我假设您具备修改您认为应该以不同方式实现的内容的编程能力。但是,请记住,就像其他懒惰的混蛋一样,我只做了我需要的事情来让代码为我的目的工作。如果我尝试的第一种方法有效,我通常会避免探索更好的方法,除非第一种有效的方法真的糟透了。
您将 SQL Express 作为在非企业环境中使用数据库后端的一种廉价/简便的方式。例如,我创建此项目的根本原因是为了维护一个存储在网络上的电影文件数据库。我编写了一个包,可以扫描一系列网络共享以查找新/更改的文件,并将文件名和文件信息记录在数据库中。我只用它来做这件事。我不需要担心权限、活动目录组或任何系统管理员的废话。如果您需要这种基础设施,您应该考虑使用全功能的零售版 SQL Server 设置及其内置的 SQL 代理支持,以及 ActiveDirectory 来管理用户。
编码注意事项
此解决方案中的代码注释相当详细,因此您应该能够轻松阅读并理解我所做的事情或原因。如果您觉得难以理解,也许您应该考虑换一份工作,因为这不是理论代码,而且我不会做真正奇怪的事情,除非绝对有必要完成工作。我坚信 KISS 原则。
最后,我不会为我的数据库访问使用企业框架。我认为它对我需要完成的事情来说太笨重和过度了。
构建块
没有必要重新发明轮子。此代码使用了几个来自第三方已有的组件,甚至包括我在此以文章和技巧形式提交的内容。本节将介绍这些组件。
第三方库
正如我所说,我很懒,说实话,我对这个项目已经感到厌烦了。碰巧的是,这种厌烦感大约是从本文系列第一部分的中间点开始的。巧合的是,NuGet 提供了大量现成的代码,任何有半点脑子程序员都会加以利用。我至少有半点脑子,并且在制作这部“电影”时使用了以下包。
- XCeed WPF 扩展工具包 - 几乎所有 WPF 应用程序都必须使用它——我离不开它。它包含了微软未能提供的许多扩展和新控件。最重要的是,它是免费的。
- CommandLine - 允许您轻松解析控制台应用程序的命令行参数。同样,免费,免费,免费。
- System.Reactive - 除其他功能外,该库允许您以多线程方式配置计时器并响应计时器事件。SQLXAgent 使用它来调度作业执行。
- ExcelDataReader - 该库使您可以将 Excel 工作表的内容加载到
DataTable
中。我使用它来实现我的 Excel 导入代码。
我应该指出,为了尽量减小本文系列下载的大小,我已将这些 NuGet 包中的适用程序集移到 3rdParty 文件夹中,并删除了下载这些包时创建的与 NuGet 相关的文件夹。这意味着给定包中的某些文件未包含在下载中,因为这会导致文件非常大。如果您想恢复 NuGet 的内容,可以重新下载,但我个人认为除非这些库添加了一些您绝对需要的大型必备功能,否则没有理由这样做。
使用的其他 CodeProject 文章/技巧
我还利用了我提交的以下 CodeProject 文章/技巧中的至少一部分代码。
- CSV 文件解析器 - 这是我在 2016 年发布的一篇文章,基于我为该项目开发的 Excel 文件导入器编写的代码,我找到了一种更有效的方法来为使用它的代码提供数据。(我可能会在未来某一天发布一篇关于此的新文章。)
- 监视输出窗口中的消息 - 我编写此技巧是该项目工作的直接结果。它允许您在调试代码时通过输出窗口轻松显示消息。
- 比较同类的两个复杂或原始对象(备选方案) - 此技巧提供了一个扩展方法,可用于比较对象是否相等,而不管其复杂性如何。
总体架构
下方是程序集引用图,说明了此套件的哪些部分被其他部分使用。还包括通过 NuGet 下载的第三方组件。

解决方案
SQLXAgent 解决方案负责构建所有核心程序集。起初,我将包的开发包含在主解决方案中,但我担心这可能会导致开发人员引入问题,如果一个核心程序集被仅想创建包的开发人员以某种方式损坏/破坏。
SQLXAgent 解决方案
此解决方案中实现了以下程序集
- SQLXAgent(WPF 应用程序)- 此应用程序用于配置代理。
- SQLXAgentSettings(类库)- 为各种应用程序提供应用程序设置对象的访问权限。
- SQLXAgentSvc(Windows 服务)- 这是根据指定的时间表运行代理的服务。
- SQLXCommon(类库)- 提供解决方案中所有其他程序集之间的通用方法和类。
- SQLXData(类库)- 提供表示代理的 Model 和 View-Model 对象。
- SQLXPkgBase(类库)- 提供基类(包括 Excel 和 CSV 导入器基类)。所有包类都应使用此库。
- SQLXPkgRunner(控制台应用程序)- 运行 SQLXAgent 包 DLL。
- SQLXThreadManager(类库)- 在服务中管理代理作业。
- TPLTesterUI(WPF 应用程序)- 测试应用程序,用于测试
SQLXThreadManager
库(如果它在这里运行,它也*应该*在服务中运行)。
- WpfCommon(WPF 类库)- 提供解决方案中 WPF 程序集使用的方法和类。
SQLXPackages 解决方案
此解决方案位于此处

我包含了一些示例包
- PkgSampleImportCSV(包)- 演示从 CSV 文件导入的示例包。
- PkgSampleImportExcel2(包)- 演示从 Excel 文件导入的示例包。
- PkgSampleSQL(包)- 演示运行 SQL 查询的示例包。
当 SQLXAgent 解决方案构建时,SQLXCommon 和 SQLXPkgBase 程序集的生成后步骤会将这些程序集复制到 DLLBin 文件夹。当您在 SQLXAgent 解决方案上执行 Clean
时,这些复制的文件不会被删除,因此在包中引用它们应该相当容易。
如果开发包的程序员需要开发多个包使用的“通用”代码,则会提供另一个程序集,名为 PkgCommon,它实现了一个名为 PkgGlobals
的静态类。它位于 SQLXCommon
命名空间中。使用此程序集的目标是避免重新编译 SQLXCommon 程序集(并可能破坏整个 SQLXAgent 解决方案)。
SQLXAgent
我重建了 SQL Server Agent 属性窗体,在合理范围内,因此对于有创建完整版 SQL Server 作业经验的人来说,此应用程序的大部分界面应该已经比较熟悉了。我为 SQL Express 和我认为合理的内容做了调整,所以如果您不喜欢界面的某些方面,您拥有所有源代码,所以……您懂的。此应用程序的用户界面将在本文系列的第 1 部分中详细讨论(本文顶部有一个链接)。
MVVM 支持 (SQLXData)
数据在 SQLXData 程序集中实现。我使用了单独的程序集,因为多个应用程序需要访问这些类。模型包含以下内容:
- JobItem - 表示一个“作业”。每个作业包含一个或多个步骤。
- JobList - 表示作业的
List
。
- StepItem - 表示作业“步骤”。
- StepList - 表示步骤的
List
。
- ConnStringItem - 表示一个连接字符串项。
- ConnStringList - 表示您指定的最后十个连接字符串的列表。
- JobHistoryItem - 表示作业级别的历史记录项。我们之所以将作业和步骤历史记录项分开,是因为要在 SQLXAgent 应用程序中显示。
- StepHistoryItem - 表示步骤级别的历史记录项。
- JobHistoryList - 表示作业级别历史记录项的列表。
- StepHistoryList - 表示步骤级别历史记录项的列表。此列表作为作业级别历史记录项类中的一个属性实现。
JobItem
和 StepItem
类派生自 ItemModelBase
,后者提供了一些与从数据库进行添加/更新/检索处理相关的通用功能。
ViewModel 是标准的 WPF 类型。简而言之,每个模型类都有一个 ViewModel 类。不多也不少。它负责处理模型,并提供额外的特殊属性供 UI 使用。我没有使用 IDataErrorInfo
,但它已在基类(WpfCommon.Notifiable
)中实现,如果您觉得有必要启用它。当然,您必须在 UI 中实现*所有*必需的编码,但这应该不言而喻。
SQLXAgentSvc
该实用程序的服务部分无疑是整个功能中相对较小的一部分。它所做的只是运行作业线程管理器。服务的安装、卸载、启动和停止由 SQLXAgent 应用程序处理。这使得管理比使用命令行进行安装/卸载,或通过 Windows 服务控制台“管理”服务更简单。
作业线程管理器
作业及其计划的管理是整个实用程序中最复杂的部分。在找到一个相对简单的实现方式之前,我对它进行了几次重新设计,并且该方式也使得您作为开发人员需要付出的额外工作更少。我的目标是让开发人员仅负责开发他们想要的包。
我在本文系列的第 1 部分中多次提到过这一点,但在这里重申一下。此实用程序*不*打算在企业环境中使用,因为 SQL Express 也*不*打算在 such 环境中使用。考虑到这一点,请注意,我预计在任何给定的安装中最多运行少量作业。我不太清楚合理的阈值是多少,但我会警惕那些拥有 5-10 个以上作业的人。但是,随意尽可能地压榨代码。我已经测试了三个作业,一切似乎都运行正常。根据您的作业实际执行的任务,您的 Mileage 将会有所不同。我对这一点不做任何道歉。
最初,我打算使用实际的多线程代码来实现正在运行的作业。然而,这很快就变得不必要地复杂和难以管理,所以我去寻找替代方案。这时我发现了 System.Reactive(又名“Rx”)。
本质上,每个作业都会计算其下一个执行日期/时间(基于作业指定的计划),并设置相当于计时器间隔的内容。当间隔到期时,Rx 会触发一个事件,作业线程项会执行其工作(按用户指定的顺序执行每个启用的步骤),并计算下一个执行时间。
运行包
如果一个步骤被配置为“PKG”,则意味着开发人员在 SQLXPackages 解决方案中开发了一个包。为了使此工作正常进行,必须满足一些要求。这些要求在本文系列的第 1 部分中已详细讨论。包的“运行”方式如下。
当需要运行包时,SQLXPkgRunner 命令行应用程序由作业线程项运行。作业线程向 SQLXPkgRunner 应用程序提供命令行参数,以便它可以动态加载适当的包。一个编写良好的包可以将其结果(0 或非零状态)传达给调用应用程序,调用应用程序又会通知作业线程对象,以便审计日志可以更新为适当的信息。编码细节在本文系列的第 4 部分中提供。
TPLTesterUI.exe - 测试应用程序

这个名字表明了我当时正在尝试的一种线程技术——我想开始测试作业线程管理器,所以不要评判我。简而言之,这个应用程序只允许您启动作业管理器并停止作业管理器。目的是为您提供一种在 IDE 的 Output 窗口中调试作业线程管理器并监视其输出的方法。

应用程序设置
与该应用程序中似乎所有其他东西一样,该应用程序的设置代码目前位于 Settings 窗体的第 3 个迭代中。不可否认,支持最新迭代的 VMSettings
类(及其支持的模型)是比较仓促地组合在一起的,我可能还没有让该应用程序的某些部分使用它。本节只是承认这一事实,而且在本文系列发布时,这可能不再是现实。
最终,设置对象被移到了它们自己的程序集中,以避免命名空间之间的循环引用。这可能不是最好的主意,但它奏效了。
VMSettings
类没有在任何地方被特别讨论,因为它在整体中的作用相对较小。
历史
- 2017 年 9 月 29 日 - 首次发布。