SSIS - 平面文件始终为ANSI编码,而不是UTF-8编码

22

有一个非常简单的SSIS包:

  • OLE DB源用于通过视图获取数据(数据库表中所有字符串列均为nvarchar或nchar)。
  • 派生列用于格式化现有日期并将其添加到数据集中,数据类型为DT_WSTR。
  • 多路广播任务用于在以下之间拆分数据集:
    • OLE DB命令用于将行更新为“已处理”。
    • Flat file目标 - 其连接管理器设置为Code Page 65001 UTF-8,Unicode未选中。所有字符串列映射到DT_WSTR。

每次运行此包并在Notepad ++中打开平面文件时,它是ANSI格式,而不是UTF-8。如果我勾选Unicode选项,则该文件是UCS-2 Little Endian。

我做错了什么-如何使平面文件成为UTF-8编码?

谢谢


好的 - 在SQL Server论坛上似乎找到了一个可接受的解决方法。基本上,我需要创建两个UTF-8模板文件,使用文件任务将它们复制到我的目标位置,然后确保我是在追加数据而不是覆盖数据。 - Neil
1
请随意回答您的问题,然后标记它。 - Sam
6个回答

32

在源代码 -> 高级编辑器 -> 组件属性中,将默认代码页设置为65001;并将AlwaysUseDefaultCodePage设置为True。

然后在源代码 -> 高级编辑器 -> 输入和输出属性中,检查每个外部列和输出列,并在可能的情况下将代码页设置为65001。

就这样了。

顺便说一句,Excel不能定义文件内的数据为UTF-8格式。Excel只是一个文件处理程序。您也可以使用记事本创建CSV文件,只要使用UTF-8填充CSV文件,就应该没问题。


3
在“源代码” -> “高级编辑器” -> “组件属性”中,将“默认代码页”设置为65001,“始终使用默认代码页”设置为True。这一步骤帮助我节省了数小时寻找双重代码页引用错误。 - BigChief
1
请注意,此方法不适用于 NVARCHAR(MAX) 列,但如果查询将其转换为 NVARCHAR(4000),则可以使用此方法。如果您的查询需要在字段中使用 > 4000 个字符,请尝试使用脚本组件或其他解决方案。 - NYCdotNet

8

向答案中添加解释...

将CodePage设置为65001(但不要在文件源上勾选Unicode复选框),应该生成一个UTF-8文件。(是的,内部数据类型也应该是nvarchar等)。

但是从SSIS生成的文件没有BOM头(字节顺序标记),因此某些程序会认为它仍然是ASCII而不是UTF-8。我已经在MSDN上看到了微软员工的确认,也经过了测试确认。

文件追加解决方案是绕过此问题的一种方法 - 通过创建带有正确BOM的空文件,然后从SSIS附加数据,BOM标头保持不变。如果您告诉SSIS覆盖该文件,它也会丢失BOM。

感谢这里的提示,它帮助我找出了上述细节。


我认为他们现在有BOM选项。 - technazi

5

最近我遇到了一个问题,具体情况如下:

您正在使用SQL Server Integration Services(Visual Studio 2005)解决方案。您正在从数据库中提取数据并尝试将结果放入UTF-8格式的平面文件(.CSV)中。该解决方案完美地导出数据并保留文件中的特殊字符,因为您使用了65001作为代码页。

但是,当您打开文本文件或尝试将其加载到另一个进程中时,它会显示文件是ANSI而不是UTF-8。如果您在记事本中打开文件并进行“另存为”,然后将编码更改为UTF-8,那么您的外部进程就可以工作,但这是一项繁琐的手动操作。

我发现,当您指定平面文件连接管理器的代码页属性时,它会生成一个UTF-8文件。但是,它生成的UTF-8文件版本缺少我们所称的字节顺序标记。

因此,如果您有包含字符AA的CSV文件,则UTF8的BOM将为0xef、0xbb和0xbf。即使文件没有BOM,它仍然是UTF8。

不幸的是,在某些旧的遗留系统中,应用程序会搜索BOM以确定文件类型。看起来您的进程也是这样做的。

为了解决问题,您可以在导出过程后运行以下代码片段的脚本任务。

using System.IO;

using System.Text;

using System.Threading;

using System.Globalization;

enter code here

static void Main(string[] args)
       {
           string pattern = "*.csv";
           string[] files = Directory.GetFiles(@".\", pattern, SearchOption.AllDirectories);
           FileCodePageConverter converter = new FileCodePageConverter();
           converter.SetCulture("en-US");
           foreach (string file in files)
           {
               converter.Convert(file, file, "Windows-1252"); // Convert from code page Windows-1250 to UTF-8  
           }  
       }

class FileCodePageConverter 
  { 
      public void Convert(string path, string path2, string codepage) 
      { 
          byte[] buffer = File.ReadAllBytes(path); 
          if (buffer[0] != 0xef && buffer[0] != 0xbb) 
          { 
              byte[] buffer2 = Encoding.Convert(Encoding.GetEncoding(codepage), Encoding.UTF8, buffer); 
              byte[] utf8 = new byte[] { 0xef, 0xbb, 0xbf }; 
              FileStream fs = File.Create(path2); 
              fs.Write(utf8, 0, utf8.Length); 
              fs.Write(buffer2, 0, buffer2.Length); 
              fs.Close(); 
          } 
      } 

      public void SetCulture(string name) 
      { 
          Thread.CurrentThread.CurrentCulture = new CultureInfo(name); 
          Thread.CurrentThread.CurrentUICulture = new CultureInfo(name); 
      } 
  }

当您运行该程序包时,您会发现指定文件夹中的所有CSV文件都将被转换为UTF8格式,其中包含字节顺序标记。
这样,您的外部进程就能够使用导出的CSV文件了。
如果您只想要特定的文件夹...将该变量发送到脚本任务并使用下面的命令。
      string sPath;

      sPath=Dts.Variables["User::v_ExtractPath"].Value.ToString();

      string pattern = "*.txt";

      string[] files = Directory.GetFiles(sPath);

我希望您能够受益!

我正在一个项目上工作,其中我将CSV文件交给Linux团队,他们最终会将它们加载到MySQL仓库中。你的方法是唯一有效的,你的代码可行,我只是添加了参数化。任何尝试创建UTF-8编码的文件,并在SSIS on-board方式下顺利加载的尝试都未成功。 - cdonner
五年后,我们开始遇到了上述实现的内存不足异常。我不得不用基于流的解决方案替换它,以便可以分段处理文件。我将在一个单独的答案中发布它。 - cdonner

0

我知道这是一个非常老的话题,但是这里有另一个答案,可能比已经发布的其他答案更容易实现(你可以选择)。

  1. 我找到了this;你可以从this location下载.exe文件。(它是免费的)。
  2. 请确保按照第一个链接中的说明,将.exe复制到C:\ Windows \ System32和C:\ Windows \ SysWOW64中,以便轻松使用而无需键入/记住复杂的路径。
  3. 在SSIS中,添加一个执行进程任务。
  4. 在“进程” ->“可执行文件”字段中使用convertcp.exe配置对象。
  5. 在“进程” ->“参数”字段中使用以下内容配置对象的参数:0 65001 / b / i“\<OriginalFilePath<OriginalFile>.csv”/ o“\<TargetFilePath<TargetFile>_UTF-8.csv”
  6. 我建议将窗口样式设置为隐藏。
  7. 完成!如果运行包,则执行进程任务将将原始ANSI文件转换为UTF-8。您也可以将其他代码页从其他代码页转换。只需找到代码页编号即可!

基本上,这个命令行实用程序为SSIS提供了使用“执行进程任务”从代码页转换到代码页的能力。对我来说非常好用。(如果您部署到SQL Server,则当然还需要将可执行文件复制到系统文件夹中的服务器中。)

祝好,Raphael


0

好的 - 在SQL Server论坛上似乎找到了一个可接受的解决方法。基本上,我不得不创建两个UTF-8模板文件,使用文件任务将它们复制到我的目标,然后确保我是在追加数据而不是覆盖。


天啊,到了2022年,我还是没有解决方案,只能用这个临时方法! - Mahesh

0

对于非常大的文件,@Prashanthi的内存解决方案会导致内存溢出异常。这是我的实现,是从这里的代码变体。

    public static void ConvertFileEncoding(String path, 
                                           Encoding sourceEncoding, Encoding destEncoding)
    {
        // If the source and destination encodings are the same, do nothting.
        if (sourceEncoding == destEncoding)
        {
            return;
        }

        // otherwise, move file to a temporary path before processing
        String tempPath = Path.GetDirectoryName(path) + "\\" + Guid.NewGuid().ToString() + ".csv";
        File.Move(path, tempPath);

        // Convert the file.
        try
        {
            FileStream fileStream = new FileStream(tempPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            using (StreamReader sr = new StreamReader(fileStream, sourceEncoding, false))
            {
                using (StreamWriter sw = new StreamWriter(path, false, destEncoding))
                {
                    //this seems to not work here
                    //byte[] utf8 = new byte[] { 0xef, 0xbb, 0xbf };
                    //sw.BaseStream.Write(utf8, 0, utf8.Length);

                    int charsRead;
                    char[] buffer = new char[128 * 1024];
                    while ((charsRead = sr.ReadBlock(buffer, 0, buffer.Length)) > 0)
                    {
                        sw.Write(buffer, 0, charsRead);
                    }
                }
            }
        }
        finally
        {
            File.Delete(tempPath);
        }
    }

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