oracle 返回中文字符为“?”的OdbcConnection

avwztpqn  于 2022-12-18  发布在  Oracle
关注(0)|答案(1)|浏览(225)

我有一个Oracle数据库,其中存储了一些简体中文的数据值。我创建了一个ASP.net MVC C#网页,该网页应该显示此信息。我使用OdbcConnection来检索数据,但是当我运行da.Fill(t)命令时,值返回为“?”

OdbcCommand cmd = new OdbcCommand();
        cmd.CommandText = select;

        OdbcConnection SqlConn = new OdbcConnection("Driver={Oracle in instantclient_11_2};Dbq=Database;Uid=Username;pwd=password;");
        DataTable t = new DataTable();
        cmd.Connection = SqlConn;

        SqlConn.Open();
        OdbcDataAdapter da = new OdbcDataAdapter(cmd);
        SqlConn.Close();
        da.Fill(t);
        return t;

t有数据,但所有应该是汉字的东西都只是一系列“?????”

njthzxwz

njthzxwz1#

字符集的问题是相当普遍的,让我试着给予一些一般性的注解。
原则上,您必须考虑四种不同的字符集设置。

1和2:NLS_CHARACTERSETNLS_NCHAR_CHARACTERSET

示例:AL32UTF8
它们在数据库中定义,您可以使用查询它们

SELECT * 
    FROM V$NLS_PARAMETERS 
    WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');

这些设置定义了哪些字符(以何种格式)可以存储在您的数据库中-不多也不少。如果您必须在现有数据库中更改它,则需要一些工作(参见Character Set Migration和/或Oracle Database Migration Assistant for Unicode)。

3:NLS_LANG

示例:AMERICAN_AMERICA.AL32UTF8
此值在您的客户机上定义。NLS_LANG与在数据库中存储字符的能力无关。它用于让Oracle知道您在客户机端使用的字符集。设置NLS_LANG值时(例如AL 32 UTF8),然后您只需告诉Oracle数据库“我的客户端使用字符集AL 32 UTF8”-这并不一定意味着您的客户端真的在使用AL 32 UTF8!(参见下面的#4)
NLS_LANG可以由环境变量NLS_LANG定义,也可以由Windows注册表在HKLM\SOFTWARE\Wow6432Node\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG(适用于32位)或HKLM\SOFTWARE\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG(适用于64位)上定义。根据应用程序的不同,可能还有其他方法可以指定NLS_LANG,但我们还是坚持使用基本方法。如果未提供NLS_LANG值,Oracle将默认为AMERICAN_AMERICA.US7ASCII
NLS_LANG的格式为NLS_LANG=language_territory.charset。NLS_LANG的{charset}部分显示在任何系统表或视图中。NLS_LANG定义的所有组件都是可选的,因此以下定义都有效:一米十氮一x,一米十一氮一x,一米十二氮一x,一米十三氮一x,一米十四氮一x。
如上所述,NLS_LANG的{charset}部分在数据库的任何系统表/视图或任何函数中都不可用。严格地说,这是真的,但是您可以运行以下查询:

SELECT DISTINCT CLIENT_CHARSET
FROM V$SESSION_CONNECT_INFO
WHERE (SID, SERIAL#) = (SELECT SID, SERIAL# FROM v$SESSION WHERE AUDSID = USERENV('SESSIONID'));

它应该从当前的NLS_LANG设置返回字符集-但是根据我的经验,该值通常为NULL或Unknown,即不可靠。
在这里找到更多非常有用的信息:NLS_LANG FAQ
请注意,某些技术不使用NLS_LANG,因此那里的设置没有任何效果,例如:

  • ODP.NET托管驱动程序不区分NLS_LANG。它只区分.NET区域设置。(请参阅.NET开发人员指南的数据提供程序)
  • OraOLEDB(来自Oracle)始终使用UTF-16(请参阅OraOLEDB提供程序特定功能)
  • 基于Java的JDBC(例如SQL Developer)有自己的方法来处理字符集(有关详细信息,请参见数据库JDBC开发人员指南-全球化支持)

4:终端、应用程序或.sql文件编码的“真实的”字符集

示例:UTF-8
如果您使用终端程序(即SQL*plus或isql),则可以使用命令chcp查询代码页,在Unix/Linux上,等效命令为locale charmapecho $LANG。您可以从此处获得所有Windows代码页标识符的列表:注意,对于UTF-8(chcp 65001),存在一些问题,请参见this discussion
如果你使用.sql文件和TOAD或SQL-Developer这样的编辑器,你必须检查保存选项。通常你可以选择UTF-8ANSIISO-8859-1等值。ANSI表示Windows ANSI代码页,通常是CP1252,你可以在HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP或这里检查你的注册表:National Language Support (NLS) API Reference

  • [Microsoft已删除此参考,请将其作为Web存档[国家语言支持(NLS)API参考] 7]*

如何设置所有这些值?

最重要的一点是匹配NLS_LANG和您的“真实的”字符集的终端,分别。应用程序或编码的.sql文件
一些常见的配对是:

  • CP 850-〉x1米35英寸1x
  • CP 1252或ANSI(如果是“西方”PC)-〉WE8MSWIN1252
  • 国际标准化组织-8859 -1-〉WE8ISO8859P1
  • 国际标准化组织-8859 -15-〉WE8ISO8859P15
  • UTF-8编码-〉AL32UTF8

或者运行此查询以获取更多信息:

SELECT VALUE AS ORACLE_CHARSET, UTL_I18N.MAP_CHARSET(VALUE) AS IANA_NAME
FROM V$NLS_VALID_VALUES
WHERE PARAMETER = 'CHARACTERSET';


有些技术可以让你的生活更轻松,例如ODP .NET(unmanged driver)或Oracle的ODBC驱动程序会自动继承NLS_LANG值的字符集,因此上述条件始终为真。

是否需要将客户端NLS_LANG值设置为等于数据库NLS_CHARACTERSET值?

不,不一定!例如,如果您有数据库字符集NLS_CHARACTERSET=AL32UTF8客户端字符集NLS_LANG=.ZHS32GB18030,那么它将毫无问题地工作(前提是您的客户端确实使用GB 18030),尽管这些字符集完全不同。GB18030是中文常用的字符集,与UTF-8一样,它支持所有Unicode字符。
如果您有,例如NLS_CHARACTERSET=AL32UTF8NLS_LANG=.WE8ISO8859P1,它也可以工作(同样,假设您的客户端确实使用ISO-8859-P1)。但是,数据库可能存储您的客户端无法显示的字符,而客户端将显示一个占位符(例如¿)。
无论如何,如果合适,匹配NLS_LANG和NLS_CHARACTERSET值是有益的。如果它们相等,您可以确保任何可能存储在数据库中的字符也可以显示,并且您在终端中输入或写入.sql文件的任何字符也可以存储在数据库中,并且不会被占位符替代。

补充

很多时候你会读到像“NLS_LANG字符集必须与你的数据库字符集相同”这样的建议(也在SO上)。这根本不是真的,这是一个流行的神话!
下面就是证明:

C:\>set NLS_LANG=.AL32UTF8

C:\>sqlplus ...

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2  CharSet VARCHAR2(20);
  3  BEGIN
  4     SELECT VALUE INTO Charset FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
  5     DBMS_OUTPUT.PUT_LINE('Database NLS_CHARACTERSET is '||Charset);
  6     IF UNISTR('\20AC') = '€' THEN
  7             DBMS_OUTPUT.PUT_LINE ( '"€" is equal to U+20AC' );
  8     ELSE
  9             DBMS_OUTPUT.PUT_LINE ( '"€" is not the same as U+20AC' );
 10     END IF;
 11  END;
 12  /

Database NLS_CHARACTERSET is AL32UTF8
"€" is not the same as U+20AC

PL/SQL procedure successfully completed.

客户机和数据库字符集都是AL32UTF8,但字符不匹配。原因是,我的cmd.exe以及SQL*Plus使用Windows CP 1252。因此,我必须相应地设置NLS_LANG:

C:\>chcp
Active code page: 1252

C:\>set NLS_LANG=.WE8MSWIN1252

C:\>sqlplus ...

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2  CharSet VARCHAR2(20);
  3  BEGIN
  4     SELECT VALUE INTO Charset FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
  5     DBMS_OUTPUT.PUT_LINE('Database NLS_CHARACTERSET is '||Charset);
  6     IF UNISTR('\20AC') = '€' THEN
  7             DBMS_OUTPUT.PUT_LINE ( '"€" is equal to U+20AC' );
  8     ELSE
  9             DBMS_OUTPUT.PUT_LINE ( '"€" is not the same as U+20AC' );
 10     END IF;
 11  END;
 12  /

Database NLS_CHARACTERSET is AL32UTF8
"€" is equal to U+20AC

PL/SQL procedure successfully completed.

再考虑一下这个例子:

CREATE TABLE ARABIC_LANGUAGE (
    LANG_CHAR VARCHAR2(20), 
    LANG_NCHAR NVARCHAR2(20));

INSERT INTO ARABIC_LANGUAGE VALUES ('العربية', 'العربية');

您需要为单个语句的NLS_LANG设置两个不同的值-这是不可能的。
另请参见如果我们有US 7ASCII字符集,为什么它允许我们存储非ASCII字符?或者NLS_NCHAR_CHARACTERSET和NLS_CHARACTERSET for Oracle之间的区别

相关问题