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

时间:2012-11-06 17:16:24

标签: ssis sql-server-2012

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

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

FileSpec 属性允许您指定一个文件掩码来指示您在集合中需要哪些文件,但是我无法完全看到如何指定表达式来使其工作,因为它本质上是一个字符串匹配。

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

  

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

3 个答案:

答案 0 :(得分:12)

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

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

我们假设有一个文件夹,其中包含以YYYYMMDD格式命名的一堆文件。该文件夹包含自1921年以来每月第一天的文件,如 19210101 19210201 19210301 ....所有当前月份< EM> 20121101 。这会增加1,103个文件。

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

Files

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

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

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

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

Parameters

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

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

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

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

  

“*”+(@ [$ Package :: ExtensionToMatch] ==“”?“。*”:@ [$ Package :: ExtensionToMatch])

  • ProcessThisFile - 此Boolean数据类型的变量将评估特定文件是否与条件匹配。

Variables

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

对于成功匹配表达式的每个文件,Foreach循环容器中的脚本任务会将变量FileCount的计数器增加1。

Foreach循环外部的脚本任务将只显示Foreach循环容器循环的文件数。

Control flow

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

Foreach Loop collection

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

Foreach Loop Variable Mappings

在Expression任务上,将表达式设置为以下值。表达式将没有扩展名的文件名转换为数字,然后检查它是否在参数MinIndexId

中的计算结果大于给定数字
  

@ [User :: ProcessThisFile] =(DT_BOOL)((DT_I4)(REPLACE(@ [User :: ActiveFilePath],@ [User :: FileSpec],“”))&gt; @ [$ Package :: MinIndexId ]?1:0)

Expression Task

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

  

@ [用户:: ProcessThisFile]

Precedence constraint

在第一个脚本任务中,我将变量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(不包括此项)时执行包时,它会输出值773

Output 1

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

希望有所帮助。

Output 2

答案 1 :(得分:2)

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

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

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

Sample Control Flow showing a Script Task to enumerate the files feeding into a ForEach Variable Enumerator

在我的脚本任务中,我使用异步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>,而我只能假设是未过滤的{ {1}}或SSIS内置 ForEach文件枚举器中的类似内容。

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

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

答案 2 :(得分:1)

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