9

我正在尝试创建一个 SSIS 包来处理包含多年文件的目录中的文件。这些文件都以数字命名,因此为了节省处理所有内容,我想向 SSIS 传递一个最小数字,并且只枚举名称(转换为数字)高于我的最小值的文件。

我试过让ForEach File循环枚举所有内容,然后在脚本任务中排除文件,但是在处理数十万个文件时,这太慢了,不适合。

FileSpec属性允许您指定一个文件掩码来指示您希望在集合中包含哪些文件,但我不太明白如何指定一个表达式来使其工作,因为它本质上是一个字符串匹配。

如果组件中有一个表达式基本上是说Should I Enumerate? - Yes / No,那将是完美的。我一直在试验下面的表达式,但找不到应用它的属性。

(DT_I4)REPLACE(SUBSTRING(@[User::ActiveFilePath],FINDSTRING(@[User::ActiveFilePath], "\", 7) + 1 ,100),".txt","") > @[User: :MinIndexId] ? “真假”

4

3 回答 3

16

这是实现这一目标的一种方法。您可以使用Expression Task组合 withForeach Loop Container来匹配文件名的数值。这是一个说明如何执行此操作的示例。该示例使用SSIS 2012.

这可能不是很有效,但它是这样做的一种方式。

假设有一个文件夹,其中包含以 YYYYMMDD 格式命名的文件。该文件夹包含自 1921 年以来每个月的第一天的文件,例如192101011921020119210301 .... 直到当前月份的所有20121101。这增加了1,103文件。

假设要求仅遍历自 1948 年 6 月以来创建的文件。这意味着 SSIS 包必须仅遍历大于19480601.

文件

在 SSIS 包上,创建以下三个参数。最好为这些配置参数,因为这些值可以跨环境配置。

  • ExtensionToMatch- 这个String数据类型的参数将包含包必须循环的扩展。这将补充FileSpec将在 Foreach 循环容器上使用的变量的值。

  • FolderToEnumerate- 此String数据类型参数将存储包含要循环遍历的文件的文件夹路径。

  • MinIndexId- 此Int32数据类型参数将包含文件应与模式匹配的最小数值。

参数

创建以下四个参数来帮助我们遍历文件。

  • ActiveFilePath- 这个数据类型的变量String将保存文件名,因为 Foreach 循环容器循环遍历文件夹中的每个文件。此变量用于另一个变量的表达式。为避免错误,请将其设置为非空值,例如 1。

  • FileCount- 这是一个Int32数据类型的虚拟变量,将用于此示例以说明 Foreach 循环容器将循环通过的文件数。

  • FileSpec- 这个数据类型的变量String将保存要循环的文件模式。将此变量的表达式设置为下面提到的值。此表达式将使用参数上指定的扩展名。如果没有扩展名,它将*.*遍历所有文件。

"*" + (@[$Package::ExtensionToMatch] == "" ? ".*" : @[$Package::ExtensionToMatch])

  • ProcessThisFile- 此数据类型变量Boolean将评估特定文件是否符合条件。

变量

如下所示配置包。Foreach 循环容器将遍历与FileSpec变量指定的模式匹配的所有文件。表达式任务上指定的表达式将在运行时进行计算,并将填充变量 ProcessThisFile。然后,该变量将用于 Precedence 约束以确定是否处理文件。

FileCountForeach 循环容器中的脚本任务将为每个成功匹配表达式的文件将变量的计数器增加1。

Foreach 循环外的脚本任务将简单地显示 Foreach 循环容器循环了多少文件。

控制流

配置 Foreach 循环容器以循环使用参数的文件夹和使用变量的文件。

Foreach 循环集合

ActiveFilePath当循环通过每个文件时,将文件名存储在变量中。

Foreach 循环变量映射

在表达式任务中,将表达式设置为以下值。该表达式会将不带扩展名的文件名转换为数字,然后检查其计算结果是否大于参数中的给定数字MinIndexId

@[User::ProcessThisFile] = (DT_BOOL)((DT_I4)(REPLACE(@[User::ActiveFilePath], @[User::FileSpec] ,"")) > @[$Package::MinIndexId] ? 1: 0)

表达任务

右键单击 Precedence 约束并将其配置为使用ProcessThisFile表达式上的变量。这告诉包仅在文件与表达式任务上设置的条件匹配时才处理文件。

@[用户::ProcessThisFile]

优先约束

在第一个脚本任务中,我将变量User::FileCount设置为ReadWriteVariables和脚本任务中的以下 C# 代码。这会增加成功匹配条件的文件的计数器。

public void Main()
{
    Dts.Variables["User::FileCount"].Value = Convert.ToInt32(Dts.Variables["User::FileCount"].Value) + 1;
    Dts.TaskResult = (int)ScriptResults.Success;
}

在第二个脚本任务中,我将变量User::FileCount设置为ReadOnlyVariables和脚本任务中的以下 C# 代码。这只是输出已处理的文件总数。

public void Main()
{
    MessageBox.Show(String.Format("Total files looped through: {0}", Dts.Variables["User::FileCount"].Value));
    Dts.TaskResult = (int)ScriptResults.Success;
}

当 MinIndexId 设置为1948061(不包括 this)执行包时,它输出 value 773

输出 1

当 MinIndexId 设置为20111201(不包括 this)执行包时,它输出 value 11

希望有帮助。

输出 2

于 2012-11-06T20:05:03.023 回答
3

通过调查 ForEach 循环在 SSIS 中的工作方式(以创建我自己的方式来解决问题),似乎它的工作方式(据我所知)是在任何掩码之前先枚举文件集合指定的。如果不查看 ForEach 循环的底层代码,很难准确判断发生了什么,但它似乎是这样做的,导致处理超过 100k 文件时性能下降。

虽然@Siva 的解决方案非常详细,并且绝对是对我最初方法的改进,但它基本上只是相同的过程,除了使用表达式任务来测试文件名,而不是脚本任务(这似乎确实提供了一些改进)。

因此,我决定采用完全不同的方法,而不是使用基于文件的 ForEach 循环,而是自己在脚本任务中枚举集合,应用我的过滤逻辑,然后遍历剩余的结果。这就是我所做的:

示例控制流显示了一个脚本任务,用于枚举提供给 ForEach 变量枚举器的文件

在我的脚本任务中,我使用异步DirectoryInfo.EnumerateFiles方法,这是大型文件集合的推荐方法,因为它允许流式传输,而不是在应用任何逻辑之前必须等待创建整个集合。

这是代码:

public void Main()
{
    string sourceDir = Dts.Variables["SourceDirectory"].Value.ToString();
    int minJobId = (int)Dts.Variables["MinIndexId"].Value;

    //Enumerate file collection (using Enumerate Files to allow us to start processing immediately
    List<string> activeFiles = new List<string>();

    System.Threading.Tasks.Task listTask = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
         DirectoryInfo dir = new DirectoryInfo(sourceDir);
         foreach (FileInfo f in dir.EnumerateFiles("*.txt"))
         {
              FileInfo file = f;
              string filePath = file.FullName;
              string fileName = filePath.Substring(filePath.LastIndexOf("\\") + 1);
              int jobId = Convert.ToInt32(fileName.Substring(0, fileName.IndexOf(".txt")));

              if (jobId > minJobId)
                   activeFiles.Add(filePath);
         }
    });

    //Wait here for completion
    System.Threading.Tasks.Task.WaitAll(new System.Threading.Tasks.Task[] { listTask });
    Dts.Variables["ActiveFilenames"].Value = activeFiles;
    Dts.TaskResult = (int)ScriptResults.Success;
}

因此,我枚举了集合,在发现文件时应用我的逻辑,并立即将文件路径添加到我的列表中以供输出。完成后,我将其分配给名为ActiveFilenames的 SSIS 对象变量,我将使用该变量作为 ForEach 循环的集合。

我将 ForEach 循环配置为ForEach From Variable Enumerator,它现在迭代一个小得多的集合(List<string>与我只能假设List<FileInfo>在 SSIS 的内置ForEach File Enumerator中未过滤或类似的东西相比,后过滤。

所以我的循环中的任务可以专门用于处理数据,因为它在进入循环之前已经被过滤了。尽管它似乎与我的初始包或 Siva 的示例没有太大不同,但在生产中(无论如何,对于这种特殊情况),过滤集合和异步枚举似乎比使用内置 ForEach 文件提供了巨大的提升枚举器。

我将继续研究 ForEach 循环容器,看看是否可以在自定义组件中复制此逻辑。如果我得到这个工作,我会在评论中发布一个链接。

于 2012-11-12T17:02:39.660 回答
1

正如你所说,你能做的最好的事情就是使用 FileSpec 来指定一个掩码。您可以在其中至少包含一些规范,例如 2010、2011 和 2012 以“201”开头的文件。然后,在其他一些任务中,您可以过滤掉那些您不想处理的文件(例如,2010)。

于 2012-11-06T18:06:42.400 回答