У нас есть несколько строк nvarchar в T-SQL, для которых некоторые символы отображаются в C# как 0xfdff (неотображаемый символ)
Вот изолированный пример одного примера 0x4edc, который отлично работает в строке T-SQL, отображается как 0xfdff в C# - проблема в том, что мы затем сохраняем это обратно в БД, и это повреждает строку.
[Test]
public void DotNetUnicodeToSqlServerTest()
{
var unmappableCharacter = Encoding.Unicode.GetString(new byte[] { 0x4e, 0xdc });
var con = new SqlConnection(@"Server=(localdb)\mssqllocaldb;Database=unicode_test;Trusted_Connection=True;");
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = $"SELECT unicode(N'{unmappableCharacter}')";
var reader = cmd.ExecuteReader();
reader.Read();
var unicode = reader.GetInt32(0);
// Fails to map to a unicode character, so it's replaced with the replacement character
Assert.That(unicode, Is.EqualTo(0xfffd));
}
[Test]
public void SqlServerToDotNetTest()
{
var con = new SqlConnection(@"Server=(localdb)\mssqllocaldb;Database=unicode_test;Trusted_Connection=True;");
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = $"SELECT convert(nvarchar(10), 0x4edc), unicode(convert(nvarchar(10), 0x4edc))";
var reader = cmd.ExecuteReader();
reader.Read();
var unmappableCharacter = reader.GetSqlString(0).Value;
// Fails to map to a unicode character, so it's replaced with the replacement character
Assert.That(Encoding.Unicode.GetBytes(unmappableCharacter), Is.EqualTo(new byte[] { 0xfd, 0xff }));
var unicode = reader.GetInt32(1);
// T-SQL is happy to process the unmappable character
Assert.That(unicode, Is.EqualTo(0xdc4e));
}
Я не уверен, что является причиной этого. Я протестировал его во всем диапазоне 0x0000-0xffff и оказалось, что 2050 из них не отображаются (остальные в порядке). Например, запуск того же кода с 0x3fe0 работает нормально.
Может кто-нибудь объяснить, что это происходит, и нужно ли что-то сделать, чтобы получить все символы сопоставить правильно.
(используя SQL Server 2019, C#6, параметры сортировки SQL_Latin1_General_CP1_CI_AS, если это помогает)
Обратите внимание, что данные не сохраняются ни в одной таблице, поэтому я не считаю, что проблема напрямую связана с сопоставлением. Кроме того, строку можно изменять и использовать в T-SQL, но только когда она отображается в C#, она ломается по непонятным мне причинам.
Мой код показывает диапазоны, которые не сопоставляться с C# (заменяется на 0xfdff)
[Test]
public void UnicodeTranslationTest()
{
var con = new SqlConnection(@"Server=(localdb)\mssqllocaldb;Database=unicode_test;Trusted_Connection=True;");
con.Open();
var startOfBrokenRange = default(string);
for (var i = 0; i < ushort.MaxValue; i++)
{
var bytes = new byte[2];
bytes[0] = (byte)(i >> 8);
bytes[1] = (byte)i;
var original = "0x" + bytes[0].ToString("X2") + bytes[1].ToString("X2");
string stringInDotNet;
using (var cmd = con.CreateCommand())
{
cmd.CommandText = $"SELECT 1 id, convert(nchar(1), {original}) text";
using var reader = cmd.ExecuteReader();
reader.Read();
stringInDotNet = (string)reader[1];
}
var translated = "0x" + string.Join("", Encoding.Unicode.GetBytes(stringInDotNet).Select(b => b.ToString("x2"))).ToUpper();
if (translated != original)
{
startOfBrokenRange ??= original;
}
else
{
if (startOfBrokenRange == null) continue;
Console.WriteLine(startOfBrokenRange + "-" + original);
startOfBrokenRange = null;
}
}
}
Создает список или диапазоны по похожему шаблону:
0x00D8-0x00E0
0x01D8-0x01E0
0x02D8-0x02E0
0x03D8-0x03E0
0x04D8-0x04E0
0x05D8-0x05E0
0x06D8-0x06E0
0x07D8-0x07E0
Может быть, здесь есть подсказка?
Дальнейшее редактирование - пытаясь выяснить разницу между строками SQL и строками C#, я попробовал некоторые другие кодировки в C#, и кажется, что кодировка Latin1 действительно обрабатывает пример символа 0x4edc, как я и ожидал (учитывая мое замешательство!)
[Test]
public void DotNetCanSupportProblemStringWithLatin1()
{
var unmappableCharacter = Encoding.Latin1.GetString(new byte[] { 0x4e, 0xdc });
var hex = "0x" + string.Join("", Encoding.Latin1.GetBytes(unmappableCharacter).Select(b => b.ToString("x2")));
Assert.That(hex, Is.EqualTo("0x4edc"));
}
[Test]
public void DotNetCanSupportProblemStringWithDefaultUTF8()
{
var unmappableCharacter = Encoding.Default.GetString(new byte[] { 0x4e, 0xdc });
var hex = "0x" + string.Join("", Encoding.Default.GetBytes(unmappableCharacter).Select(b => b.ToString("x2")));
Assert.That(hex, Is.EqualTo("0x4eefbfbd"));
}
Подробнее здесь: https://stackoverflow.com/questions/781 ... -to-dotnet