OdbcConnection возвращает китайские иероглифы как "?"

У меня есть база данных 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 есть данные, но все, что должно быть китайскими иероглифами, это просто серия "?????"

1 ответ

Решение

Проблемы с набором символов довольно распространены, позвольте мне сделать несколько общих замечаний.

В принципе, вы должны рассмотреть четыре различных набора символов.

1 и 2: NLS_CHARACTERSET а также NLS_NCHAR_CHARACTERSET

Пример: AL32UTF8

Они определены только в вашей базе данных, вы можете опросить их с

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

Эти параметры определяют, какие символы (в каком формате) могут храниться в вашей базе данных - ни больше, ни меньше. Это требует определенных усилий (см. Миграция набора символов и / или Oracle Database Migration Assistant для Unicode), если вам нужно изменить его в существующей базе данных.

3: NLS_LANG

Пример: AMERICAN_AMERICA.AL32UTF8

Это значение определяется только на вашем клиенте. NLS_LANG не имеет ничего общего с возможностью хранить символы в базе данных. Он используется, чтобы дать Oracle знать, какой набор символов вы используете на стороне клиента. Когда вы устанавливаете значение NLS_LANG (например, AL32UTF8), вы просто говорите базе данных Oracle "мой клиент использует набор символов AL32UTF8" - это не обязательно означает, что ваш клиент действительно использует AL32UTF8! (см. ниже № 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, Часть { charset } NLS_LANG не отображается ни в одной системной таблице или представлении. Все компоненты определения NLS_LANG являются необязательными, поэтому допустимы следующие определения: NLS_LANG=.WE8ISO8859P1, NLS_LANG=_GERMANY, NLS_LANG=AMERICAN, NLS_LANG=ITALIAN_.WE8MSWIN1252, NLS_LANG=_BELGIUM.US7ASCII,

Как указано выше, часть {charset} NLS_LANG недоступен в базе данных ни в одной системной таблице / представлении или любой функции. Строго говоря, это правда, однако вы можете выполнить этот запрос:

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

4: "реальный" набор символов вашего терминала, вашего приложения или кодировки .sql файлы

Пример: UTF-8

Если вы работаете в терминале Windows (т.е. с SQL*plus), вы можете запросить кодовую страницу с помощью команды chcp в Unix/Linux это эквивалентно locale charmap или же echo $LANG, Вы можете получить список всех идентификаторов кодовых страниц Windows здесь: Идентификаторы кодовых страниц. Обратите внимание, для UTF-8 (chcp 65001) Есть некоторые проблемы, смотрите это обсуждение.

Если вы работаете с .sql файлы и редактор, такие как TOAD или SQL-Developer, вы должны проверить параметры сохранения. Обычно вы можете выбрать такие значения, как UTF-8, ANSI, ISO-8859-1, так далее. ANSI означает кодовую страницу Windows ANSI, обычно CP1252, вы можете проверить в своем реестре на HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP или здесь: Справочник по поддержке национальных языков (NLS)

[Microsoft удалила эту ссылку, примите форму веб-архива Справочник по поддержке национальных языков (NLS) ]

Примечание: в зависимости от вашей технологии вам не нужно особо заботиться об этой теме, вот несколько примеров:

Как установить все эти значения?

Наиболее важным моментом является соответствие NLS_LANG и ваш "реальный" набор символов вашего терминала, соответственно. приложение или кодировка вашего .sql файлы

Некоторые общие пары:

  • CP850 -> WE8PC850

  • CP1252 или ANSI (в случае "западного" ПК) -> WE8MSWIN1252

  • ISO-8859-1 -> WE8ISO8859P1

  • ISO-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';

Требуется ли установить клиентское значение NLS_LANG равным базе данных NLS_CHARACTERSET значение?

Нет, не обязательно! Например, если у вас есть набор символов базы данных NLS_CHARACTERSET=AL32UTF8 и клиентский набор символов NLS_LANG=.ZHS32GB18030 тогда он будет работать без каких-либо проблем (при условии, что ваш клиент действительно использует GB18030), хотя эти наборы символов совершенно разные. GB18030 - это набор символов, обычно используемый для китайского языка, например UTF-8 он поддерживает все символы Unicode.

Если у вас есть, например NLS_CHARACTERSET=AL32UTF8 а также NLS_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 CP1252. Поэтому я должен установить 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 для одного заявления - что невозможно.

Другие вопросы по тегам