博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

使用Script Component源处理不规则平面文件

Posted on 2015-08-28 18:23  Jason.Liao  阅读(1011)  评论(0编辑  收藏  举报

微软 BI 系列随笔 - SSIS 2012 高级应用 - Script Component处理不规则平面文件


场景介绍

在使用SSIS从平面文件导入源数据时,最常遇到的是以下两种情况:

  1. 导入规则的平面文件,这种文件的每一行数据的解析规则都是一样的
  2. 导入不规则的平面文件,这种文件可能包含多种数据结构,比如某些行是头(Head),某些行是内容(Content),某些行是尾(Tail),三种数据的解析格式都不一样
  3. 源文件的字符编码集不一样,例如文件是来自于不同的系统(或区域),这些文件的编码有可能是ANSI,Unicode或者UTF-8中的任何一种。

对于第一种情况,可以使用平面文件连接器(FlatFile Connection)来解析,通过配置他的字符集,格式,分隔符等就能做到。

对于第二种情况,要是使用平面文件连接器(FlatFile Connection)的话,需要使用临时文件,即使用平面文件连接器从源文件中中将每一行的数据读为一个类型,然后根据内容使用条件分离(Conditional Split)将内容分离,重新组织新的文件。然后正对新生成的文件做常规处理实现数据导入。

对于第三种情况,比较复杂,目前是不能使用平面文件连接器来解析的。平面文件连接器虽然支持表达式设置CodePage和是否是Unicode,以及其他的设置,但是具体操作起来比较麻烦。设计时有严重的干扰,经常常常导致编译不通过。

本文将针对第二种情况和第三种情况给读者一个思路,即使用Script Component作为数据源解析文件,输出数据,同时结合处理第二种情况。

实施步骤

分析平面文件格式

假设我们有如下格式的平面文件:

首先这个文件的编码是不规则的,他含有西班牙文(还有其他的文件,是不含这个文字集的)。

第二,他有02H和02D两种内容格式,姑且认为是Header和Detail。

所以说这种类型的文件使用平面文件连接器根本无法解析。

那么我们怎么办呢?看下面。

Script Component源

定义输入与输出

SSIS中支持使用Script Component作为数据源,你可以使用C#或者VB代码去做任何事情,然后将构造一个或多个数据集作为输出。如下所示:

本例中我定义了一个EDCOrder的输出用来放Header的内容,OrderDetail的内容我不需要所以没有定义。

我另外定义了一个FailedRows来放Header解析失败的的内容。没有输入。

编写脚本

在脚本页选择要使用的变量和脚本语言。

点击编辑脚本(Edit Script...)

这时候会打开一个Vsta project的脚本编辑项目。

这个项目包含三个文件

  • ComponentWrapper.cs - 包含变量类,连接类和用户控件类
  • BufferWrapper.cs - 定义了输出类
  • main.cs - 脚本文件的入口函数,继承自用户控件类。

main.cs这个文件有三个方法

  • PreExecute - 执行之前
  • PostExecute - 执行之后
  • CreateNewOutputRows 创建输出行

为了解析平面文件,我们定义了如下参数,主要是一个StreamReader来读文件,时间记录和行号:

    System.IO.StreamReader sr;
    DateTime now;
    int rowNumber,failedRows;
    Guid fileId;
    string ContentLengthError;
    string CoposIdNullError;
    string errorMsg;

在PreExecute中这么写,根据传进来的文件名打开文件,定义错误消息,初始化变量

    /// <summary>
    /// This method is called once, before rows begin to be processed in the data flow.
    ///
    /// You can remove this method if you don't need to do anything here.
    /// </summary>
    public override void PreExecute()
    {
        base.PreExecute();

        now = DateTime.Now;
        rowNumber = 1;
        failedRows = 0;
        fileId = Guid.Parse(Variables.pvFileID.ToString());
        ContentLengthError = "Line content validation failed. Line content length is not equal to 1141.";
        CoposIdNullError = "Line content validation failed. Copos id is required.";
        errorMsg = "Filename: " + Variables.pvLoadFile + System.Environment.NewLine;
        sr = new System.IO.StreamReader(Variables.pvLoadFile);
    }

在PostExecute中这么写,关闭文件,返回错误信息给脚本调用的变量

    /// <summary>
    /// This method is called after all the rows have passed through this component.
    ///
    /// You can delete this method if you don't need to do anything here.
    /// </summary>
    public override void PostExecute()
    {
        base.PostExecute();

        sr.Close();
        Variables.pvFailedRows = failedRows;
        if (errorMsg != "Filename: " + Variables.pvLoadFile + System.Environment.NewLine)
            Variables.pvErrorMessage += System.Environment.NewLine + errorMsg;
    }

在CreateNewOutputRows这么写,根据每一行的内容判断消息类型,验证消息内容,生成输出行。

    public override void CreateNewOutputRows()
    {
        /*
          Add rows by calling the AddRow method on the member variable named "<Output Name>Buffer".
          For example, call MyOutputBuffer.AddRow() if your output was named "MyOutput".
        */
        while (!sr.EndOfStream)
        {
            string line = sr.ReadLine();
            if (line.StartsWith("02H"))
            {
                if (line.Length != 1141)
                {
                    FailedRowsBuffer.AddRow();
                    FailedRowsBuffer.RowNumber = rowNumber;
                    FailedRowsBuffer.ErrorDescription = ContentLengthError;
                    FailedRowsBuffer.Content = line;
                    failedRows++;
                    errorMsg += "At Row: " + rowNumber.ToString() + System.Environment.NewLine + "Error Description: " + ContentLengthError + System.Environment.NewLine;
                }
                else
                {
                    if (!string.IsNullOrEmpty(line.Substring(3, 30)) && !string.IsNullOrWhiteSpace(line.Substring(3, 30))) //Coposid is required
                    {
                        EDCOrderBuffer.AddRow();
                        EDCOrderBuffer.Coposid = line.Substring(3, 30);
                        EDCOrderBuffer.Custname = line.Substring(33, 64);
                        EDCOrderBuffer.Custtel = line.Substring(257, 20);
                        EDCOrderBuffer.Dlvytitle = line.Substring(287, 4);
                        EDCOrderBuffer.Dlvyinitial = line.Substring(291, 1);
                        EDCOrderBuffer.Dlvyname = line.Substring(292, 30);
                        EDCOrderBuffer.Dlvyhouse = line.Substring(322, 30);
                        EDCOrderBuffer.Dlvytel = line.Substring(482, 20);
                        EDCOrderBuffer.Dsptchsrvc = line.Substring(882, 9);
                        EDCOrderBuffer.Dlvydate = line.Substring(891, 8);
                        EDCOrderBuffer.Dmworder = line.Substring(959, 30);
                        EDCOrderBuffer.Dsptchemail = line.Substring(989, 70);
                        EDCOrderBuffer.OrderDate = line.Substring(1059, 12);
                        EDCOrderBuffer.FileId = fileId;
                        EDCOrderBuffer.CreatedOn = now;
                    }
                    else
                    {
                        FailedRowsBuffer.AddRow();
                        FailedRowsBuffer.RowNumber = rowNumber;
                        FailedRowsBuffer.ErrorDescription = CoposIdNullError;
                        FailedRowsBuffer.Content = line;
                        failedRows++;
                        errorMsg += "At Row: " + rowNumber.ToString() + System.Environment.NewLine + "Error Description: " + CoposIdNullError + System.Environment.NewLine;
                    }
                }
            }
            rowNumber++;
        }
    }

编译下下这个Vsta项目。

处理Script Component输出

这样,一个不规则的文件就被我们使用Script Component轻松的处理了。

  • 由于使用StreamReader,使用默认编码格式他会自动识别文件编码,避免了使用平面文件解析器出现乱码的情况
  • 由于针对文件内容分别处理,可以解析不规则文件,产生多个输出
  • 由于加入了自定义验证信息,可以验证消息的内容

疑问:

  • 使用Script Component能否平行执行,EDCOrderBuffer.AddRow()的时候是不是就已经产生了一行输出了呢?这个有待研究
  • 要写代码解析文件,有没有可以长度类型都是可以配置的,如果需要格式转换是否方便,或者在外部再做转换呢?

将这些问题留给读者朋友们去思考吧。

也可以给我留言大家一起讨论哦。