下面对此进行概述。有些事情可能会因您的环境以及您希望如何记录有错误的文件夹和文件的名称而异,但这应该可以让您朝着正确的方向开始。
创建一个int变量,你可以调用这个“ErrorCount”并确保它的值设置为0。这将在稍后的错误记录中使用。
创建一个执行 SQL 任务,该任务将创建一个用于保存文件夹和文件名的表。 “文件名”是 T-SQL 中的关键字,因此使用了标识符(方括号)。这些不是必需的,但更容易阅读。
- 由于您已经在使用 Foreach 循环来加载文件,我假设已经映射了一个变量来保存每次迭代的文件名。如果您还没有这样做,请在 Foreach 循环的“变量映射”窗格的索引 0 处添加一个字符串变量。
在加载文件的数据流任务上,添加一个带有 C# 脚本任务的 OnError 事件处理程序。在 Script Task 编辑器中,在 ReadOnlyVariables 字段中添加保存文件名的字符串变量(Foreach 循环中的索引 0)。在ReadWriteVariables 字段中添加您之前创建的“ErrorCount”变量和System::Propagate 系统变量。这将在稍后使用以允许在发生错误后处理后续文件。
此脚本任务的代码在本文后面。您需要添加对 System.IO 和 System.Data.SqlClient 命名空间的引用。 每个错误都会触发一次 OnError 事件,而不仅仅是在文件出现错误时触发。在此示例中,仅当“ErrorCount”用户变量的值为 0 时才记录文件名。记录文件和文件夹后,此变量设置为 1,以避免多次记录同一文件。 Path.GetDirectoryName 和Path.GetFileName 方法分别用于获取文件夹和文件名。虽然您正在加载的所有文件很可能具有相同的扩展名,但Path.GetFileName 仍用于返回带有文件名的文件扩展名。如果您不想看到文件扩展名,请改用Path.GetFileNameWithoutExtension 方法。
SSPI 设置为集成安全以指示将使用 Windows 身份验证。要使用 SQL Server 身份验证,请将其设置为 false 并为 User ID 和 Password 属性添加适当的值。我建议使用参数,即下面的 SqlParameter 对象,而不是将 SQL 命令构造为连接字符串。输入是默认的ParameterDirection,但为了清楚起见,仍然定义了这些。此脚本获取已解析的文件夹和文件名并将它们插入到一个表中,在此示例中名为 ErrorFiles,该表将用于保存在包执行期间出现错误的文件,然后再将这些文件写入文件。虽然使用 Execute SQL Task 进行插入会更简单,但System.IO 命名空间中的方法允许在文件路径更改时正确获取文件夹和文件名。如果您使用的文件夹从未更改,则可以使用执行 SQL 任务和插入中的 T-SQL SUBSTRING 函数来解析包含这些变量的文件夹和文件名。
在数据流任务之后,在 Foreach 循环中添加另一个脚本任务。将其连接到数据流任务并将 Precedence Constraint 上的评估操作更改为 Expression 并添加以下表达式。这将通过检查“ErrorCount”变量的值来确保只有在 Foreach 循环的迭代期间出现错误时才会运行以下脚本任务。
在此脚本任务中,在 ReadWriteVariables 字段中添加“ErrorCount”,类似于之前将其设置为 1,现在将其设置回 0 (Dts.Variables["User::ErrorCount"].Value = 0)。这样做是为了允许在 Foreach 循环的后续迭代中导致错误的文件也被记录下来。
在 Foreach 循环之后,添加一个具有 OLE DB 源的数据流任务。将数据访问模式设置为 SQL 命令,并从用于存储有错误文件的表中选择 FolderName 和 FileName 列。如果您没有单独创建此表,则可以将 ValidateExternalMetadata 设置为 false,然后从 Advanced Editor 的 Input and Output Properties 选项卡的 External and Output Columns 文件夹中添加输出列。但是,如果您对此不熟悉,则只需运行 CREATE TABLE DDL 并以这种方式定义元数据可能会更容易。在此数据流任务中,添加一个连接到 OLE DB 源的平面文件目标。创建一个新的平面文件管理器,其中包含要用于保存包含错误的文件的名称的文件名。您可能希望在文件名中使用表达式以使其在每次执行包时都是唯一的。本文后面有一个示例表达式。这仅使用当前日期。如果您计划在一天内多次运行,您可能需要添加时间戳或以其他方式使每个文件名唯一。通过将此表达式添加到变量中并将其用作平面文件管理器的ConnectionString 属性,可以将其设置为输出文件的名称。
接下来在此数据流任务之后添加一个执行 SQL 任务。对于SQLStatement,添加 SQL 以删除用于保存包含错误的文件名的表(如果存在)。虽然这不是必需的,因为创建此表的初始任务会删除它,但这可以避免在您的数据库中出现额外的表。
优先约束表达式:
@[User::ErrorCount] > 0
平面文件连接管理器连接字符串表达式:
"C:\\Your Folder\\" + (DT_STR, 4, 1252)DATEPART("Year", GETDATE()) + "_"
+ (DT_STR, 2, 1252)DATEPART("Month", GETDATE()) + "_"
+ (DT_STR, 2, 1252)DATEPART("Day", GETDATE()) +"_"
+ "ErrorFiles.txt"
错误文件表 DDL:
IF(OBJECT_ID(N'YourDatabase.DBO.ERRORFILES') IS NOT NULL)
BEGIN
DROP TABLE YourDatabase.DBO.ERRORFILES
END
CREATE TABLE YourDatabase.DBO.ERRORFILES
(
FOLDERNAME VARCHAR(100),
[FILENAME] VARCHAR(100)
)
OnError 事件处理脚本任务代码:
if (Convert.ToInt32(Dts.Variables["User::ErrorCount"].Value.ToString()) == 0)
{
string connString = @"Data Source=YourSQLServerInstance;Initial Catalog=YourDatabase;Integrated Security=SSPI";
string cmd = @"Insert into dbo.ErrorFiles (FolderName,[FileName]) values (@folderName, @fileName)";
//parse variable with file that caused error
string errorFileFullName = Dts.Variables["User::NameForTable"].Value.ToString();
//get folder
string errorFolderName = Path.GetDirectoryName(errorFileFullName);
//get only file name
string errorFileName = Path.GetFileName(errorFileFullName);
using (SqlConnection conn = new SqlConnection(connString))
{
SqlCommand sql = new SqlCommand(cmd, conn);
SqlParameter pFolderName = new SqlParameter("@folderName", SqlDbType.VarChar);
pFolderName.Direction = ParameterDirection.Input;
pFolderName.Value = errorFolderName;
pFolderName.Size = 100;
SqlParameter pFileName = new SqlParameter("@fileName", SqlDbType.VarChar);
pFileName.Direction = ParameterDirection.Input;
pFileName.Value = errorFileName;
pFileName.Size = 100;
sql.Parameters.Add(pFolderName);
sql.Parameters.Add(pFileName);
conn.Open();
sql.ExecuteNonQuery();
//avoid failing Foreach Loop so other files are processed
Dts.Variables["System::Propagate"].Value = false;
//prevent event handler from firing multiple times.
Dts.Variables["User::ErrorCount"].Value = 1;
}
}