【问题标题】:How can I set an expression to the FileSpec property on Foreach File enumerator?如何将表达式设置为 Foreach 文件枚举器上的 FileSpec 属性?
【发布时间】:2012-11-06 17:16:24
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: ssis sql-server-2012


    【解决方案1】:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    右键单击优先约束并将其配置为在表达式上使用变量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(不包括这个)执行包时,输出值773

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

    希望对您有所帮助。

    【讨论】:

    • 感谢您提供非常详细的回复!这基本上就是我目前正在做的事情(尽管使用脚本任务来评估是否处理文件,而不是表达式任务),但是在处理数十万个文件时它太慢而无法使用。我试试表情任务,看看是不是更快。不过,理想情况下,我正在寻找某种方法来评估 ForEach File 枚举器中的表达式,因为它无论如何都需要对每个项目执行检查,以减少集合的大小。
    • 是的,我不确定。我想,不知何故,在 .NET 框架的某个地方,它本质上会归结为一个比较操作(因此是一个布尔结果),如果我可以修改该属性,我就可以做到。这可能是在 SSIS 中使用的实现不可能,可能是我需要创建一个自定义组件来执行类似的操作。我正在研究这个组件,看看它是否能解决问题:microsoft-ssis.blogspot.co.uk/2012/04/…
    • 我现在已经解决了这个问题,方法是切换到 ForEach 变量枚举器,在它之前有一个脚本任务,它使用 DirectoryInfo.EnumerateFiles 获取所有文件详细信息并评估我的表达式,将 IEnumerable 结果输出到一个对象变量。这似乎比在 ForEach File 循环中执行检查要快一个数量级。不完全是一个修复,而是一个可行的解决方法。
    【解决方案2】:

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

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

    因此,我决定采用完全不同的方法,而不是使用基于文件的 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&lt;FileInfo&gt; 相比,后过滤的 List&lt;string&gt;或 SSIS 的内置 ForEach File Enumerator 中的类似内容。

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 如果它可以像 Powershell 一样工作就好了:01,02,03,04 | % { dir "datafile2018$($_).txt" }
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-21
      • 1970-01-01
      • 2020-03-17
      • 1970-01-01
      • 1970-01-01
      • 2023-03-05
      相关资源
      最近更新 更多