SSIS - 文件存在性检查未能正确控制包任务流程

3
有一个现有的 SSIS 包,基于源文件执行多个文件操作。当该文件在预期目录中找不到时,它经常会失败。我只想在其中加入一些智能功能,使其可以发送缺失文件的电子邮件通知,而不是失败。
我尝试了许多不同的脚本任务,包括 VB、C 和 SQL,但没有一项能够稳定地控制流程。有时它有效,而在其他情况下则无效。下面是我的变量列表——前三个变量是我原本想要使用的,但我添加了 FullPath 以简化事情。不幸的是,这并没有改变什么。
我的测试:我从目录中删除源文件并在 VS 中执行包,它会调用发送电子邮件任务来通知我文件不存在。然后我将文件放回原处并执行程序包,它再次调用发送电子邮件任务,好像文件不存在一样。我对断点和监视窗口不是很熟悉,所以我为 FilePath 和 FileExists 放置了两个消息框——FilePath 返回的值是正确的,带有文件名,但之后立即返回的 FileExists 消息框值为 0。请注意,在同时告诉我它看不到文件的这个时刻,我已经检查了磁盘,并可以看到它在那里。
这是重点:我已经在这上面花了好几天的时间,昨天在测试时,它突然就能用了!我将文件放入源目录中,它正确地运行了整个过程。我从源目录中删除文件,它调用了发送邮件任务并成功完成。我多次成功地测试了这两种情况,但现在又失败了。我不明白为什么,也没有时间和兴趣继续测试这个仅在间歇性工作的文件存在检查脚本任务。我甚至试图获取我听说过的 File Properties 任务(https://archive.codeplex.com/?p=filepropertiestask),但它与当前软件版本不兼容。我已经尝试了 VS 2019 和 SSDT 2017,无论哪个都不支持 File Properties。或者,我只是不知道如何安装它。
有人可以提供建议吗?
变量-
- FileName 字符串,fileName.txt - FilePath 字符串,C:\directory path\ - FileExists 布尔值,False(虽然我尝试了 int32、甚至 char N/Y) - FullPath 字符串,C:\Directory path\filename.txt
C 脚本任务尝试-
public void Main()
{
    // TODO: Add your code here
    String Filepath = Dts.Variables["User::FilePath"].Value.ToString() + Dts.Variables["User::FileName"].Value.ToString();
    if (
    File.Exists(Filepath))
    {
        Dts.Variables["User::FileExists"].Value = 1;
    }
    else
        Dts.Variables["User::FileExists"].Value = 0;

    Dts.TaskResult = (int)ScriptResults.Success;
}

或者

//TODO: Add your code here
String Filepath = Dts.Variables["User::FilePath"].Value.ToString() + Dts.Variables["User::FileName"].Value.ToString();
if (
FileExists(Filepath))
{
    Dts.Variables["User::FileExists"].Value = 1;
}

MessageBox.Show(Filepath);
MessageBox.Show(Dts.Variables{"User::FileExists"].Value.ToString());

Dts.TaskResult = (int)ScriptResults.Success;
}

甚至只需要这么简单:
Dts.Variables("FileExists").Value = File.Exists(Dts.Variables("FilePath").Value).ToString
Dts.TaskResult = (int)ScriptResults.Success;

VB脚本任务 -

Public Sub Main()
    ' Fill WriteVariable with value from ReadVariable 
    Dts.Variables("User::FileExists").Value = Dts.Variables("User::FullPath").Value
 
    Dts.TaskResult = ScriptResults.Success
End Sub

执行 SQL 任务 -

    DECLARE
      @FilesExist BIT = 0,
      @FolderPath VARCHAR(100) = 'C:\directory path\'

DECLARE @Files TABLE ([FileName] VARCHAR(100), Depth INT, [File] INT)
INSERT INTO @Files
EXEC master.sys.xp_dirtree @FolderPath,1,1;

IF EXISTS(
      SELECT 1 FROM @Files
        WHERE [FileName] = 'fileName.txt'
      AND Depth = 1
      AND [File] = 1
  )
            SET @FilesExist = 1
            RETURN;

脚本任务优先约束:

  • 评估操作:表达式和限制条件
  • 值:成功
  • 表达式:@[User::FileExists]==1
  • 逻辑 AND

评估操作:表达式和限制条件

  • 值:成功
  • 表达式:@[User::FileExists]==0
  • 逻辑 AND

这是我控制流程的截图,其中脚本任务文件存在性检查是流程中的第7个项目。文件名中没有日期,它总是“filename.txt”。该文件由流程中的第4个任务通过合并其他文件创建,我刚刚得知需要在此处添加类似的检查,但有多个文件,所以我需要在包中的第3个任务之前进行通配符检查。

enter image description here

如果可以的话,请在问题中放入您的控制流程截图。我想验证任务的布局。 - billinkc
那么对于解决方案,您有任何偏好的路线吗?代码、现有组件?核心是要检查文件是否存在。如果不存在,则通知。如果存在,则继续执行其他逻辑,对吧? - billinkc
非常抱歉,我直到现在才看到这些问题。请给我一点时间来泛化它,然后我会发布我的控制流程。 - sqldba
1个回答

0

虽然我喜欢 Script Tasks 的优雅性,但考虑到当前的任务集,我认为你可以使用现成的工具。

Foreach Loop Container

这是解决方案中的工作马。它具有内置文件枚举器,因此如果您的源文件实际上是 SourceFile_YYYYMMDD.txt 或类似的文件名,您只需使用 sourcefile*.txt,即可轻松找到它。

我喜欢它的原因是,您可以将所有逻辑放在此容器中,如果找到文件,则会执行预期的工作,无需定义先导/后继任务即可运行。

我创建了另一个名为 CurrentFile 的变量,并将其初始化为空字符串。这个空字符串起始值对于包的成功至关重要。当 Foreach Loop 找到一个文件时,它会将完整的文件名放入该变量中。

当包运行并完成所有文件操作工作时,如果找到文件,则 CurrentFile 变量的当前值不会是空字符串。如果没有找到文件,则 CurrentFile 的值保持起始值。

我将您现有的FileExists布尔型SSIS变量修改为表达式驱动。在属性中,我使用了以下表达式@[User::CurrentFile] != ""。如果CurrentFile的值不是我们的起始值,则它会评估为true。否则,它保持为false。
使用这两个“技巧”,我们可以得到一个如下所示的SSIS包。

enter image description here

我有两条路径从Foreach循环容器中出去。两者都指定成功为约束条件,然后我们使用@[User::FileExists]来表示正常情况(找到文件),并使用其反转!@[User::FileExists]来表示不存在的情况。将通知逻辑放在容器内,该容器表示“未找到文件路径”

因为我喜欢Biml,所以我附上了Biml以创建此解决方案。

虽然不太有用,因为您需要将其打补丁到现有包中,但是您应该能够创建一个最小可行的包,以处理检查和警报,如果未找到文件。然后,您可以将工作示例与当前实现进行比较。

<Biml xmlns="http://schemas.varigence.com/biml.xsd">
    <Packages>
        <Package Name="so_62505561">
            <Variables>
                <Variable Name="FileExists" DataType="Boolean" EvaluateAsExpression="true">@[User::CurrentFile] != ""</Variable>
                <Variable Name="FileName" DataType="String">so_62505561.txt</Variable>
                <Variable Name="FilePath" DataType="String">C:\ssisdata\input</Variable>
                <Variable Name="FileSpecification" DataType="String">so_62505561*.txt</Variable>
                <Variable Name="FullPath" DataType="String"></Variable>
                <Variable Name="CurrentFile" DataType="String"></Variable>
            </Variables>
            <Tasks>
                <ForEachFileLoop Name="FELC Do File Work" FileSpecification="*.txt" ConstraintMode="LinearOnSuccess" Folder="C:\tmp">
                    <Expressions>
                        <Expression ExternalProperty="FileSpec">@[User::FileSpecification]</Expression>
                        <Expression ExternalProperty="Directory">@[User::FilePath]</Expression>
                    </Expressions>
                    <VariableMappings>
                        <VariableMapping Name="0" VariableName="User.CurrentFile" />
                    </VariableMappings>
                    <Tasks>
                        <Container Name="Placeholder for work">
                        </Container>
                    </Tasks>
                </ForEachFileLoop>
                <!-- this is the unhappy path -->
                <Container Name="No file found path">
                    <PrecedenceConstraints>
                        <Inputs>
                            <Input EvaluationOperation="ExpressionAndConstraint" EvaluationValue="Success" Expression="!@[User::FileExists]" OutputPathName="FELC Do File Work.Output" />
                        </Inputs>
                    </PrecedenceConstraints>
                </Container>
                <Container Name="File found path">
                    <PrecedenceConstraints>
                        <Inputs>
                            <Input EvaluationOperation="ExpressionAndConstraint" EvaluationValue="Success" Expression="@[User::FileExists]" OutputPathName="FELC Do File Work.Output" />
                        </Inputs>
                    </PrecedenceConstraints>
                </Container>
            </Tasks>
        </Package>
    </Packages>
</Biml>

使用脚本任务测试文件是否存在

假设您有以下条件:

  • FileName 字符串,fileName.txt
  • FilePath 字符串,C:\directory path\
  • FileExists 布尔值,False

脚本任务应该将 FileName 和 FilePath 设置为只读变量,将 FileExists 设置为读/写变量。

// I favor System.IO.Path.Combine for path manipulation as it figures out the correct separator to use
string filepath = System.IO.Path.Combine(Dts.Variables["User::FilePath"].Value.ToString(),  Dts.Variables["User::FileName"].Value.ToString());
if (System.IO.File.Exists(filepath))
{
    Dts.Variables["User::FileExists"].Value = true;
}

// I favor emitting to the log I can audit it. Also, GUI events are not allowed when run from jobs
// Log the state of all our SSIS variables
bool fireAgain = false;
string message = "{0}::{1} : {2}";
foreach (var item in Dts.Variables)
{
    Dts.Events.FireInformation(0, "SCR Echo Back", string.Format(message, item.Namespace, item.Name, item.Value), string.Empty, 0, ref fireAgain);
}

// Log the file path that was built
    Dts.Events.FireInformation(0, "SCR Echo Back", string.Format(message, "local", "filepath", filepath), string.Empty, 0, ref fireAgain);

Dts.TaskResult = (int)ScriptResults.Success;

此时,当脚本运行时,您的日志中将有4个信息事件:我们3个SSIS作用域变量的状态和我们构建路径的状态。如果依赖于@[User::FileExists]的下游部分未按预期工作,则所有值都在一个方便的位置。

此时,文件检查脚本任务已退出,并且应使用以下组合标记先决约束为Expression和Constraint:

  • Success + @[User::FileExists](文件存在路径)
  • Success + !@[User::FileExists](警报路径)

日志记录注意事项

触发信息事件会导致信息在Visual Studio中运行的包的两个不同位置发出。

输出窗口和进度选项卡。

输出窗口将显示类似于

SSIS package "C:\Users\bfellows\source\repos\Integration Services Project1\Integration Services Project1\NewPackage.dtsx" starting.
Information: 0x0 at Script Task 1, SCR Echo Back: User::FileExists : False
Information: 0x0 at Script Task 1, SCR Echo Back: User::FileName : fileName.txt
Information: 0x0 at Script Task 1, SCR Echo Back: User::FilePath : C:\directory path\
Information: 0x0 at Script Task 1, SCR Echo Back: local::filepath : C:\directory path\fileName.txt
SSIS package "C:\Users\bfellows\source\repos\Integration Services Project1\Integration Services Project1\NewPackage.dtsx" finished: Success.

而进度选项卡是一个GUI元素

Progress tab in VS. Shows the same events as shown above plus Progress events

最终解决方案

@sqldba 发现在第四步中,VBA 使用了本地变量 FilesExist 而不是 FileExists。由于 VB 对未声明的变量宽容,这就是事情变得“奇怪”的原因。


关于将CurrentFile初始化为空字符串并更改为表达式驱动的建议,这在我的现有包中是否可用,用于文件存在性检查的脚本任务? - sqldba
我不明白为什么它不能与您当前的软件包一起使用。在我的想法中,ForLoop将替换脚本任务,并且先决条件将指导工作被执行或警报部分。从“如果未找到发送电子邮件”的操作堆栈将掉入我称之为“未找到文件路径”的容器中,而其他路线则通往模糊的逻辑块。容器是提供任务逻辑封装的便捷工具。 - billinkc
1
您可以使用多个容器来封装逻辑位。我已经更新了脚本,根据您提供的信息进行了调整,但这种方法应该有助于确定运行时情况是否符合预期。 - billinkc
1
很高兴听到这个消息。我发现数据人员一旦知道如何使用事件记录运行信息,就会想出很棒的策略来跟踪包运行的所有相关指标。 - billinkc
抱歉,Dale。我不确定如何做到这一点。请给予建议。 - sqldba
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接