【问题标题】:Problems importing xlsx file into SQL Server将 xlsx 文件导入 SQL Server 时出现问题
【发布时间】:2016-05-27 21:03:39
【问题描述】:

我收到一个每月需要使用 SSIS 包导入 SQL Server 的 XLSX 文件。不幸的是,发件人不遵循文件名或工作表的 UNC 命名,而且我们最近迁移到 SQL Server 2012 导致包失败 - 即使使用 Excel 连接管理器也是如此。我们也尝试过向他们发送模板,但他们拒绝遵循,我们也无法强迫他们这样做。

我一直在尝试对包进行更新,该更新将使用脚本任务将两个 Excel 工作表中的每一个导入到 System.Object 中,然后我可以查询或循环将数据导入目标 SQL Server 表。

到目前为止,使用 Microsoft here 的示例,我已经成功地将 Excel 文件路径/名称以及两个工作表名称导入到对象变量中。但是,这不会创建包含来自任一工作表的实际数据集的对象。

根据此处和网络上其他地方的示例,我已经启动了一个 C# 脚本,我相信它会将工作表数据输出到一个 Object 变量中,但是我对 C# 不是很精通,并且很难在没有要复制的完整示例。到目前为止,这是我的代码:

using System;
using System.Data;
using System.Data.OleDb; 
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;

[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
        public DataSet Main()
        {
            string fileName;
            string connectionString;

            fileName = Dts.Variables["ExcelFile"].Value.ToString();
            Console.WriteLine(fileName);

            connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;" +
                "Data Source=" + fileName + ";Extended Properties=Excel 12.0 Xml";
            Console.WriteLine(connectionString);           
            DataSet data = new DataSet();
            using (OleDbConnection con = new OleDbConnection(connectionString))
            {
                con.Open();
                OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM [Sheet1$]", connectionString);
                adapter.Fill(data);
            }

            return data;
        }
}

代码构建成功,但是当我运行包时收到一个无法描述的错误

错误:脚本任务中的 0x1:调用的目标已引发异常。
任务失败:脚本任务

我的任何一个Console.WriteLine 命令都没有得到任何输出,所以我相信脚本任务会立即失败。我确实有延迟验证 = True,尽管更改它并没有什么不同。您在我的脚本中看到任何明显/新手错误吗?我已经使用 SQL 和 SSIS 多年,但我的 C#/VB/Java/etc.知识和经验有限。

另外,如果我忽略了在 SSIS 中完成此任务的更好方法(Excel 连接除外,它不起作用),请告诉我。

更新 - 2016 年 5 月 31 日:我认为我今天有一点时间来处理这个项目,并且取得了一些进展。我更新了我的脚本任务以包括以下内容:

        DataSet data = new DataSet();
        using (OleDbConnection con = new OleDbConnection(connectionString))
        {
            con.Open();
            OleDbDataAdapter adapter = new OleDbDataAdapter(query, con);
            //OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM [Indemnity Scores$]", con);
            adapter.Fill(data);
            Dts.Variables["ExcelDataTable_IndemnityScores"].Value = data;
        }

脚本任务现在成功完成,所以我后来添加了一个 Foreach 循环容器,将其设置为 Foreach From Variable Enumerator,并选择 ExcelDataTable_IndemnityScores 作为集合。

但是,现在我很难从这个 Object 变量中提取数据。它有(或至少应该有)两列,我在变量映射中设置,并且正在使用执行 SQL 命令将值插入到表中。不幸的是,每列只插入一个空白值。

接下来我将执行 SQL 替换为一个简单的脚本任务,以返回每个变量的值。不幸的是,它返回的不是值,而是“Microsoft.SqlServer.Dts.Runtime.Variable”。我认为这是我的新手错误,但我还没有在网上找到任何解释该错误的信息?

2016 年 6 月 14 日更新: 我终于完成了这个包,它昨天在生产中成功运行。我最终使用了此处提供的建议以及其他地方的示例。我的一般工作流程需要三重嵌套的 Foreach 循环来获取从源工作簿导入的两个工作表 - 我只希望每月一个,但没有 100% 符合此任务。

我最外层的循环只是枚举我的导入目录以查找 FTP 进程下载的文件。它包含两个脚本任务。第一个只是确认 FTP 进程下载的第一个电子表格的文件名。我将上面的 Microsoft 链接用于我的代码,只对我的变量名进行了少量修改。

第二个任务从第一个电子表格中获取所有工作表名称,并且也是使用上面的 Microsoft 链接构建的。但是,我排除了任何带有“#”的工作表名称,以防止将 XML 数据库分配给我的变量。

第二个循环(第一个内部循环)枚举在第一个循环中解析的每个工作表名称。它包含三个脚本任务,第一个任务将数据从第一个工作表导入到我的对象变量中。

public void Main() { 尝试 {

            string fileName;
            string connectionString;
            string worksheetName;
            string query;

            fileName = Dts.Variables["ExcelFile"].Value.ToString();
            //MessageBox.Show("InsertWorksheetDataIntoObject - Filename: " + fileName);

            connectionString = String.Format("Provider=Microsoft.ACE.OLEDB.12.0;" +
                "Data Source={0};Extended Properties=Excel 12.0 Xml;", fileName);
            //MessageBox.Show("Connection: " + connectionString);

            worksheetName = Dts.Variables["ExcelTable"].Value.ToString();
            worksheetName = worksheetName.Replace("'", "");
            //MessageBox.Show("InsertWorksheetDataIntoObject - Worksheet: " + worksheetName);

            query = string.Format("SELECT * FROM [" + worksheetName + "]");
            //MessageBox.Show("Query: " + query);

            DataSet data = new DataSet();
            using (OleDbConnection con = new OleDbConnection(connectionString))
            {
                con.Open();
                OleDbDataAdapter adapter = new OleDbDataAdapter(query, con);
                adapter.Fill(data);
                Dts.Variables["ExcelDataTable"].Value = data;
            }

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

        catch (Exception ex)
        {
            Dts.Events.FireError(-1, "ErrorMessage", ex.ToString(), "", 0);
            Dts.TaskResult = (int)ScriptResults.Failure;
        }


        //return data;

    }

此循环中的第二个脚本任务只是从 Excel 中删除所有空白行。我本可以将它与上面的脚本合并,但我保留了它的便携性,以便将来在其他地方重用。

此循环中的第三个脚本任务使用工作表名称设置一个变量,该变量在下一个循环中用于确定我的目标表。

第三个循环(第二个内部循环)枚举包含工作表数据的对象变量中的行。它包含一个执行 SQL 任务,该任务根据上面工作表名称设置的变量值将来自两个源列的数据导入到正确的目标表中。由于工作表名称并不总是一致的,因此此循环直接连接到我的对象变量,这样就无需按名称调用源列。相反,我只是将每个变量分配给 Foreach 循环中的目标变量,然后将这些数据逐行传递到我的表中。

再次感谢大家的帮助和建议!

【问题讨论】:

  • 您是否尝试过启用此excel-sql-server.com/…(临时、动态参数、允许进行中等)并在 SSMS 中从该文件中获取数据?

标签: c# sql-server excel ssis ssis-2012


【解决方案1】:

通常当我立即收到该消息时,这意味着我在您的 ExcelFile 中将变量名拼写错误。当我执行 sql 查询并返回 null 时,我也会收到此错误。最好的办法是在写行执行之前注释掉你的代码部分,然后你至少知道是什么代码导致了问题。

我不明白为什么 excel 连接不起作用。如果文件位于 UNC 路径上并导致问题,您可以使用脚本任务将文件移动到可以工作的位置。

【讨论】:

  • 很好地了解变量 Joe C!我确实在上一个任务中使用了一个名为“ExcelFile”的变量,而这个任务使用“ExcelFiles”仅供参考 - 这些是我开始使用的 Microsoft 示例中的变量名,而不是我将在实际包中使用的变量名。
【解决方案2】:

Joe C 可能是对的,您可能用错误的名称引用了一个变量。你把变量/参数传递给脚本任务了吗?

不过,我不明白您为什么不使用数据流脚本任务之一。您可以定义输入和输出列,然后将它们填充到脚本任务的代码中:

    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".
        */
    }

这些输出行可以转移到数据流中的下一个任务——就像 SSIS 喜欢的那样。此外,使用变量要容易得多。您可以通过this.Variables.ExcelFile (f. e.) 像通常的属性一样访问它们。

另一个注意事项:不要忘记设置(控制流的)脚本任务的结果。您的任务可能会成功完成,但在以下序列流中不会有任何限制。

Dts.TaskResult = (int)ScriptResults.Success;

【讨论】:

  • 我相信数据流脚本任务需要 Excel 连接,对吗?我们收到的文件名不遵循标准的 UNC 命名,因此 Excel 连接失败 - 或者至少看起来是这样。如果我手动重命名文件,删除空格和特殊字符,Excel 连接将正常工作...只要有一个远离以确保客户端将使用我们的模板。
  • 位置总是一样的吗?为什么不使用脚本任务来重命名文件?然后获取一个 Excel 源代码,然后是另一个(脚本)任务。
  • 这可能是约翰内斯的一个可能的解决方案,尽管我还需要重命名工作表,因为它们的名称中也有空格。我将探索该选项,看看我遇到了什么问题。谢谢!
  • 重命名 Excel 工作簿中的工作表应该不是 .Net 库的问题。我很高兴能帮上忙。
【解决方案3】:

哇,当你的工作因为这样的人而变得复杂时,你不讨厌它!因此,有很多方法可以解决您的问题,我个人的观点是脚本任务可能会让您更容易遵循逻辑并完成,但@Johannes 还提出了另一种好方法。脚本任务有两个地方,它们是完全不同的编码和思维过程方法。一个是控制流中可用的“脚本任务”,这似乎是您编码并将对象添加到变量的地方。
第二个是“脚本组件”,它在数据流任务中可用。 前者您需要将其视为一个独立的脚本,它的行为完全独立于其他一切,而后者嵌入在数据流任务中,并且是什么充当源、目的地或转换。这意味着它可以用于填充要使用的记录集变量(对象)。

因此,在选项 1 中,您当前进行的方式是完成代码所需的全部内容是添加一些 c# 来更新/填充您想要的 SQL 表。这是我从其中一个包中窃取的一些代码:

            SqlConnection sqlConnection = new SqlConnection(sqlConnectionString);
        sqlConnection.Open();

        SqlBulkCopy bulkCopy = new SqlBulkCopy(sqlConnection);
        bulkCopy.DestinationTableName = _stagingTableName;
        foreach (DataColumn col in _jobRecDT.Columns)
        {
            //System.Windows.Forms.MessageBox.Show(col.ColumnName);
            bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
        }


        bulkCopy.WriteToServer(_jobRecDT);

        sqlConnection.Close();

对于选项 2,我曾经对此有所了解,也许 @Johannes 有一个链接,或者有人可以在这里发表评论。但是在这种方法中,您应该能够重用大量代码,然后将其移至“脚本组件”。然后在对象上定义记录集架构,并像在数据流任务中使用任何其他源一样使用它。

有 2 个问题需要考虑,并且需要一些进一步的逻辑。 1) 如果使用选项 1,您需要在使用批量复制或动态管理列映射之前将表/数据集重命名为您期望的名称。 2) 在数据流选项中,您需要在填充最终记录集变量之前转换数据集以始终具有相同的列和数据类型。

这两个选项都需要考虑性能和数据有效性。第一个可能是更好的性能,但 SSIS 不处理数据有效性/错误检查。选项 2,您将受益于 SSIS 错误检查和大型数据集的性能。如果您的数据集非常大,则需要调整这两个选项。还有其他考虑因素,例如线程,但我认为这不适用于您。

我希望这会有所帮助。

【讨论】:

  • 感谢 Matt 提供的详细示例!
  • 我很高兴我记得我第一次必须在 SSIS 中找出脚本时非常有趣 :)
【解决方案4】:

我编辑了我的原始问题,概述了最终对我有用的解决方案。如果有人有问题或想了解更多详细信息/示例,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多