如何在Foreach文件枚举器上将表达式设置为FileSpec属性?

GSh*_*gan 6 ssis sql-server-2012

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

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

文件规范属性用于指定文件掩码来决定你在收集想要的文件,但我不能完全看到如何用表达式来使这项工作,因为它本质上是一个字符串匹配.

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

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

小智 12

这是你可以实现这一目标的一种方法.您可以使用Expression Taskcombined with Foreach Loop Container来匹配文件名的数值.这是一个说明如何执行此操作的示例.样本使用SSIS 2012.

这可能效率不高,但它是这样做的一种方式.

我们假设有一个文件夹,其中包含以YYYYMMDD格式命名的一堆文件.该文件夹中包含了自1921年以来每个月的第一天像文件19210101,19210201,19210301 ....所有的高达当月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约束,以确定是否处理该文件.

对于FileCount成功匹配表达式的每个文件,Foreach循环容器中的脚本任务将使变量的计数器递增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;
}
Run Code Online (Sandbox Code Playgroud)

在第二个脚本任务中,我将变量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;
}
Run Code Online (Sandbox Code Playgroud)

当MinIndexId设置为1948061(不包括此项)执行包时,它输出该值773.

输出1

当MinIndexId设置为20111201(不包括此项)执行包时,它输出该值11.

希望有所帮助.

输出2


GSh*_*gan 4

通过研究 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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