Как сохранить данные Unicode в Oracle?
Я пытаюсь сохранить данные Unicode (греческий) в базе данных оракула (10 г). Я создал простую таблицу:
http://i25.tinypic.com/dvpqnk.png
Я понимаю, что NVARCHAR2 всегда использует кодировку UTF-16, поэтому она должна подойти для всех (человеческих) языков.
Затем я пытаюсь вставить строку в базу данных. Я жестко закодировал строку ("Как дела?" По-гречески) в коде. Затем я пытаюсь вернуть его из базы данных и показать.
class Program
{
static string connectionString = "<my connection string>";
static void Main (string[] args) {
string textBefore = "Τι κάνεις;";
DeleteAll ();
SaveToDatabase (textBefore);
string textAfter = GetFromDatabase ();
string beforeData = String.Format ("Before: {0}, ({1})", textBefore, ToHex (textBefore));
string afterData = String.Format ("After: {0}, ({1})", textAfter, ToHex (textAfter));
Console.WriteLine (beforeData);
Console.WriteLine (afterData);
MessageBox.Show (beforeData);
MessageBox.Show (afterData);
Console.ReadLine ();
}
static void DeleteAll () {
using (var oraConnection = new OracleConnection (connectionString)) {
oraConnection.Open ();
var command = oraConnection.CreateCommand ();
command.CommandText = "delete from UNICODEDATA";
command.ExecuteNonQuery ();
}
}
static void SaveToDatabase (string stringToSave) {
using (var oraConnection = new OracleConnection (connectionString)) {
oraConnection.Open ();
var command = oraConnection.CreateCommand ();
command.CommandText = "INSERT into UNICODEDATA (ID, UNICODESTRING) Values (11, :UnicodeString)";
command.Parameters.Add (":UnicodeString", stringToSave);
command.ExecuteNonQuery ();
}
}
static string GetFromDatabase () {
using (var oraConnection = new OracleConnection (connectionString)) {
oraConnection.Open ();
var command = oraConnection.CreateCommand ();
command.CommandText = "Select * from UNICODEDATA";
var erpReader = command.ExecuteReader ();
string s = String.Empty;
while (erpReader.Read ()) {
string text = erpReader.GetString (1);
s += text + ", ";
}
return s;
}
}
static string ToHex (string input) {
string bytes = String.Empty;
foreach (var c in input)
bytes += ((int)c).ToString ("X4") + " ";
return bytes;
}
}
Вот разные выводы:
Текст перед отправкой в базу данных в окне сообщения: http://i31.tinypic.com/2ymdo2u.png
Текст после получения из базы данных в окне сообщения: http://i28.tinypic.com/2wmq9a0.png
Вывод на консоль: http://i29.tinypic.com/6xz3px.png
Пожалуйста, не могли бы вы подсказать, что я могу делать здесь не так?
6 ответов
Я вижу пять потенциальных областей для проблем:
Как вы на самом деле получаете текст в ваше приложение.NET? Если он жестко закодирован в строковом литерале, вы уверены, что компилятор принимает правильную кодировку для вашего исходного файла?
Может быть проблема в том, как вы отправляете его в базу данных.
Может быть проблема с тем, как он хранится в базе данных.
Может быть проблема с тем, как вы выбираете его в базе данных.
Может быть проблема с тем, как вы снова отображаете это потом.
Теперь области 2-4 звучат так, как будто они менее вероятны, чем проблемы 1 и 5. Как вы отображаете текст впоследствии? Вы на самом деле извлекаете его из базы данных в.NET, или вы используете Toad или что-то подобное, чтобы попытаться увидеть это?
Если вы снова пишете это из.NET, я предлагаю вам полностью пропустить базу данных - если вы просто отображаете саму строку, что вы видите?
У меня есть статья, которая может оказаться полезной при устранении неполадок Unicode. В частности, сконцентрируйтесь на каждом месте, где кодировка может работать неправильно, и убедитесь, что всякий раз, когда вы "отображаете" строку, вы выводите точные символы Unicode (как целые числа), чтобы вы могли проверять их, а не только то, что хочет ваш текущий шрифт для отображения.
РЕДАКТИРОВАТЬ: Хорошо, поэтому база данных где-то участвует в проблеме.
Янастоятельно рекомендую вам удалить что-либо вроде ASP и HTML из уравнения. Напишите простое консольное приложение, которое ничего не делает, но вставляет строку и извлекает ее снова. Сделайте, чтобы дамп отдельных символов Unicode (как целые числа) до и после. Затем попробуйте посмотреть, что находится в базе данных (например, с помощью жабы). Я не знаю функций Oracle для преобразования строк в последовательности отдельных символов Unicode, а затем преобразования этих символов в целые числа, но вполне возможно, что это будет следующая вещь, которую я попробую.
РЕДАКТИРОВАТЬ: еще два предложения (приятно видеть консольное приложение, кстати).
Укажите тип данных для параметра, а не просто предоставьте ему объект. Например:
command.Parameters.Add (":UnicodeString", OracleType.NVarChar).Value = stringToSave;
Попробуйте использовать собственный драйвер Oracle вместо встроенного в.NET. Вы можете сделать это в любом случае, так как я считаю, что обычно считается более быстрым и надежным.
После некоторых исследований мы идем:
строка ввода = "•"; char s = input[0];
//table kuuku with column kuku(nvarchar2(100))
string connString = "your connection";
//CLEAN TABLE
using (System.Data.OracleClient.OracleConnection cn = new System.Data.OracleClient.OracleConnection(connString))
{
cn.Open();
System.Data.OracleClient.OracleCommand cmd = new System.Data.OracleClient.OracleCommand("delete from kuku ", cn);
cmd.ExecuteNonQuery();
cn.Close();
}
//INSERT WITH PARAMETER BINDING - UNICODE SAVED
using (System.Data.OracleClient.OracleConnection cn = new System.Data.OracleClient.OracleConnection(connString))
{
cn.Open();
System.Data.OracleClient.OracleCommand cmd = new System.Data.OracleClient.OracleCommand("insert into kuku (kuku) values(:UnicodeString)", cn);
cmd.Parameters.Add(":UnicodeString", System.Data.OracleClient.OracleType.NVarChar).Value = input + " OK" ;
cmd.ExecuteNonQuery();
cn.Close();
}
//INSERT WITHOUT PARAMETER BINDING - UNICODE NOT SAVED
using (System.Data.OracleClient.OracleConnection cn = new System.Data.OracleClient.OracleConnection(connString))
{
cn.Open();
System.Data.OracleClient.OracleCommand cmd = new System.Data.OracleClient.OracleCommand("insert into kuku (kuku) values('" +input+" WRONG')", cn);
cmd.ExecuteNonQuery();
cn.Close();
}
//FETCH RESULT
using (System.Data.OracleClient.OracleConnection cn = new System.Data.OracleClient.OracleConnection(connString))
{
cn.Open();
System.Data.OracleClient.OracleCommand cmd = new System.Data.OracleClient.OracleCommand("select kuku from kuku", cn);
System.Data.OracleClient.OracleDataReader dr = cmd.ExecuteReader();
if(dr.Read())
{
string output = (string) dr[0];
char sa = output[0];
}
cn.Close();
}
}
Вы можете определить, какой набор символов ваша база данных использует для NCHAR, с помощью запроса:
SQL> SELECT VALUE
2 FROM nls_database_parameters
3 WHERE parameter = 'NLS_NCHAR_CHARACTERSET';
VALUE
------------
AL16UTF16
чтобы проверить правильность конфигурации базы данных, вы можете запустить в SQL*Plus следующее:
SQL> CREATE TABLE unicodedata (ID NUMBER, unicodestring NVARCHAR2(100));
Table created
SQL> INSERT INTO unicodedata VALUES (11, 'Τι κάνεις;');
1 row inserted
SQL> SELECT * FROM unicodedata;
ID UNICODESTRING
---------- ---------------------------------------------------
11 Τι κάνεις;
Еще одна вещь, которую стоит отметить.
Если вы используете клиент oracle и хотите включить символы Unicode в CommandText, вы должны добавить следующую строку в начало вашего приложения:
System.Environment.SetEnvironmentVariable("ORA_NCHAR_LITERAL_REPLACE", "TRUE");
Это позволит вам, в случае необходимости, использовать следующий синтаксис:
command.CommandText = "INSERT into UNICODEDATA (ID, UNICODESTRING) Values (11, N'Τι κάνεις;')";
На чтение записей попробуйте
Encoding utf = Encoding.Default;
var utfBytes = odatareader.GetOracleString(0).GetNonUnicodeBytes();//OracleDataReader
Console.WriteLine(utf.GetString(utfBytes));
Решение: установите NLS_LANG!
Детали: у меня была та же самая проблема, и фактически у меня была та же самая ситуация, которая описана в расследовании Сергея Базарника. С помощью переменных связывания это работает, а без них - нет.
РЕШЕНИЕ - установить NLS_LANG в нужном месте. Так как у меня есть сервер Windows, я установил его в реестре Windows под HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ORACLE\KEY_OraClient11g_home1
Обратите внимание, что расположение реестра может отличаться, поэтому самый простой способ - найти в реестре строку "ORACLE_HOME". Также другие системы, такие как Linux, Unix могут установить это по-другому (экспорт NLS_LANG ...)
В моем случае я положил "NLS_LANG"="CROATIAN_CROATIA.UTF8"
, Так как у меня не было этой переменной, установлено значение по умолчанию. После изменения реестра вам необходимо перезапустить процесс. В моем случае я перезапустил IIS.
Причина, по которой он работает с переменными связывания, может заключаться в том, что на самом деле это происходит на стороне сервера, а на самом деле это происходит на стороне клиента. Таким образом, даже эта БД может вставлять правильные значения - до того, как это произойдет, клиент вносит нежелательные исправления, поскольку считает, что должен это сделать. Это потому, что по умолчанию NLS_LANG использует более простую кодовую страницу. Но вместо того, чтобы выполнять полезную задачу, это создает проблему, которая (как показано в исследовании выглядит трудно понять).
Если у вас есть несколько версий Oracle, обязательно исправьте все версии в реестре (в моем случае Oracle 10 имел допустимую настройку, но в Oracle 11 вообще не было установлено значение NLS_LANG).