SQLXAgent - SQL Express 的作业 - 第 5 部分(共 6 部分)
创建和运行类似 SQL Server Enterprise 的作业 - 包如何运行。
第 1 部分 - 使用 SQLXAgent
第 2 部分 - 架构和设计决策
第 3 部分 - CSV 和 Excel 导入器代码
第 4 部分 - 作业调度代码
第 5 部分 - 包的运行方式 (本文)
第 6 部分 - 有趣的编码
引言
本文是描述 SQLXAgent 实用程序的系列文章的第 5 部分。在本文中,我将描述用于运行 SQLXAgent 包的代码。
需要牢记的重要一点是,SQLXAgent 包与您(可能)所了解的 DTSX 包没有任何合理的相似之处。最接近的类比是说所有 SQLXAgent 包都只包含一个任务,而该任务是一个脚本任务(因为您必须编写代码)。可以使用您可能喜欢的任何 .Net 语言创建包,只要它们编译成 .PKG 程序集(它只不过是一个具有“PKG”扩展名的 DLL)。
注意: 本文中提供的代码片段可能不会完全反映代码的最新和最佳版本。如果它与实际代码不完全匹配,那么它会非常接近。这就是在撰写文章时注意到问题的情况。
包的运行方式
包是通过 SQLXPkgRunner 命令行应用程序运行的。一切都从 JobThread.ExecuteJob
方法开始,在该方法中调用该应用程序。从下面的代码片段中省略了不相关的代码,但本文系列的第 4 部分中讨论了相关内容。
/// <summary>
/// Executes all of the steps for the job in (step.Position) sequential order.
/// </summary>
public virtual void ExecuteJob()
{
if (!this.IsWorking)
{
...
foreach(StepItem step in this.Job.Steps)
{
if (step.StepIsEnabled)
{
...
switch (step.StepType)
{
case "SQL" :
{
...
}
break;
case "PKG" :
{
try
{
// this should never happen, but we check nonetheless
if (string.IsNullOrEmpty(step.SsisFilePath))
{
...
}
else
{
string pkgDLLFileName = step.SsisFilePath;
string path = System.IO.Path.Combine(Globals.AppPath,
"SQLXPkgRunner.exe");
// we need to pass the package's (fully qlaified) filename,
// the step ID, and the step connection string to the
// SQLXPkgRunner app
string args = string.Format("-p\"{0}\" -s\"{1}\" -c\"{2}\"",
pkgDLLFileName,
step.ID,
step.ConnectionString);
// configure and start the app's process
Process app = new Process();
ProcessStartInfo info = new ProcessStartInfo()
{
Arguments = args,
CreateNoWindow = true,
FileName = path,
UseShellExecute = true,
};
app.StartInfo = info;
app.Start();
// we want to wait for it to exit
app.WaitForExit();
// and deal with the results.
int result = app.ExitCode;
if (result > 0)
{
status = "FAIL";
SQLXExceptionEnum exception = Globals.IntToEnum(result,
SQLXExceptionEnum.Unknown);
switch (exception)
{
case SQLXExceptionEnum.PkgFileNotFound :
reason = string.Concat(SQLXExceptionCodes.Codes[(int)exception],
" - ",
pkgDLLFileName);
break;
default : reason = SQLXExceptionCodes.
Codes[(int)exception] ; break;
}
}
else
{
status = "SUCCESS";
reason = string.Empty;
}
}
}
catch (Exception ex)
{
status = "FAIL";
reason = ex.Message;
}
// DebugMsgs...
}
break;
}
...
}
else
{
...
}
}
...
}
}
SQLXPkgRunner 应用程序
此应用程序没有窗口或界面,因为它基本上是在 Windows 服务中执行的。当应用程序启动时,会对参数执行一些健全性检查。
static int Main(string[] args)
{
var options = new CommandlineOptions();
CommandLine.Parser.Default.ParseArguments(args, options);
Globals.SetExtensionFileSystemObjects(Assembly.GetExecutingAssembly());
// assume success
int result = 0;
// if we have arguments
if (args.Length > 0)
{
// if the package path is null/empty, error
if (string.IsNullOrEmpty(options.Package))
{
result = (int)SQLXExceptionEnum.CmdLineArgPkgFilename;
}
// if the specified dll does not exist, error
else if (!File.Exists(options.Package))
{
result = (int)SQLXExceptionEnum.PkgFileNotFound;
}
// if the step ID is null/empty, error
else if (string.IsNullOrEmpty(options.StepID))
{
result = (int)SQLXExceptionEnum.CmdLineArgStepID;
}
// if the step's connectionstring is null/empty, error
else if (string.IsNullOrEmpty(options.ConnectionString))
{
result = (int)SQLXExceptionEnum.CmdLineArgPkgConnString;
}
// make sure we can get the SQLXAgent connection string,
// but I don't remember why... :)
else
{
string connStr = "";
// result will be 0 (ok), 5 (config file not found),
// or 6 (conn string not found in config file)
result = GetSQLXConnString(ref connStr);
}
// if we get here and result is still 0, load the package and
// run it.
if (result == 0)
{
result = LoadDllAndRun(options.Package.Trim(),
options.StepID.Trim(),
options.ConnectionString.Trim());
}
}
else
{
result = (int)SQLXExceptionEnum.NoCmdLineArgs;
}
return result;
}
如果一切都检查通过,我们将加载并运行程序集包。假设(并且建议)给定的包只包含一个派生自 SQLXAgentPkgBase
的对象,因为我们只查找找到的第一个对象,而这就是我们使用的对象。此方法中的大部分代码都处理可能的异常。
private static int LoadDllAndRun(string path, string stepID, string connString)
{
int result = 0;
SQLXAgentPkgBase pkg = null;
// load the package DLL
var dll = Assembly.LoadFile(path);
// assume it's invalid
bool foundObject = false;
try
{
// see if an object derived from SQLXAgentPkgBase exists in the
// package DLL
Type type = dll.GetExportedTypes().FirstOrDefault(x=>x.BaseType.Name.IsLike("%SQLXAgentPkgBase"));
if (type != null)
{
// try to instantiate the object
pkg = (SQLXAgentPkgBase)(Activator.CreateInstance(type));
if (pkg != null)
{
// try to run the package
foundObject = true;
pkg.Run(stepID, connString);
string failReason = pkg.FailReason;
}
}
// can't find an appropriate class in the package asembly
if (!foundObject)
{
result = (int)SQLXExceptionEnum.SQLXPkgBaseClassNotFound;
}
}
// all of the exceptions that might be thrown here, and the return
// codes that are applicable
catch (BadImageFormatException)
{
result = (int)SQLXExceptionEnum.PkgRunnerBadImageFormat;
}
catch (System.IO.FileNotFoundException)
{
result = (int)SQLXExceptionEnum.PkgRunnerFileNotFound;
}
catch (System.IO.FileLoadException)
{
result = (int)SQLXExceptionEnum.PkgRunnerFileLoad;
}
catch (ArgumentException)
{
result = (int)SQLXExceptionEnum.PkgRunnerArgument;
}
catch (NotSupportedException)
{
result = (int)SQLXExceptionEnum.PkgRunnerNotSupported;
}
catch (System.Reflection.TargetInvocationException)
{
result = (int)SQLXExceptionEnum.PkgRunnerTargetInvocation;
}
catch (MethodAccessException)
{
result = (int)SQLXExceptionEnum.PkgRunnerMethodAccess;
}
catch (System.Runtime.InteropServices.InvalidComObjectException)
{
result = (int)SQLXExceptionEnum.PkgRunnerInvalidComObject;
}
catch (MissingMethodException)
{
result = (int)SQLXExceptionEnum.PkgRunnerMissingMethod;
}
catch (TypeLoadException)
{
result = (int)SQLXExceptionEnum.PkgRunnerTypeLoad;
}
catch (Exception)
{
result = (int)SQLXExceptionEnum.PkgRunnerUnexpected;
}
return result;
}
正如您所看到的,运行一个包是 SQLXAgent 机器中一个很小的齿轮。其中最有趣的部分是按需加载包 DLL,并确保它具有预期的派生类。
历史
- 2017 年 9 月 29 日 - 首次发布。