Точность DateTime в NHibernate и поддержке DateTime2 в NHibernate SchemeExport

Файлы не содержат символов, они содержат байты. Чтобы получить символы из байтов, вам нужно применить некоторую кодировку. Если вы применяете разные кодировки к одним и тем же байтам, вы можете получить разные символы в результатах.

Взяв в качестве примера вашу строку байтов:

PS> $ByteArray = [Byte[]]('24-4D-65-73-73-61-67-65-20-3D-20-5B-49-4F-2E-46-69-6C-65-5D-3A-3A-52-65-61-64-41-6C-6C-54-65-78-74-28-22-24-50-53-53-63-72-69-70-74-52-6F-6F-74-5C-F0-F3-F1-F1-EA-E8-E9-2E-74-78-74-22-2C-20-5B-53-79-73-74-65-6D-2E-54-65-78-74-2E-45-6E-63-6F-64-69-6E-67-5D-3A-3A-44-65-66-61-75-6C-74-29-0D-0A-24-4D-65-73-73-61-67-65' -split '-' | % { [Byte]::Parse(

Файлы не содержат символов, они содержат байты. Чтобы получить символы из байтов, вам нужно применить некоторую кодировку. Если вы применяете разные кодировки к одним и тем же байтам, вы можете получить разные символы в результатах.

Взяв в качестве примера вашу строку байтов:

[110]

Важно использовать правильную кодировку при чтении файлов. И еще одна важная вещь: ваш файл скрипта использует кодовую страницу 1251, а не UTF-8. Также обратите внимание, что последовательность байтов F0-F3-F1-F1-EA-E8-E9 (которая представляет мир русский в кодовой странице 1251) является недопустимой последовательностью байтов в соответствии с UTF-8, поэтому вместо этого вы получаете семь заменяющих символов (U+FFFD).

Поскольку PowerShell Core по умолчанию использует UTF-8, а в вашем файле сценария нет спецификации для указания иного (хотя нет спецификации, которая позволяет PowerShell распознавать кодовую страницу 1251), PowerShell Core читает ваш файл с использованием кодировки UTF-8, таким образом, он пытается получить доступ к �������.txt (которого у вас нет) вместо русский.txt.

Вы можете легко наблюдать это сами, если измените сценарий на запись файла вместо чтения.

PS> $ByteArray2 = [Byte[]](91, 73, 79, 46, 70, 105, 108, 101, 93, 58, 58, 87, 114, 105, 116, 101, 65, 108, 108, 84, 101, 120, 116, 40, 34, 36, 80, 83, 83, 99, 114, 105, 112, 116, 82, 111, 111, 116, 92, 240, 243, 241, 241, 234, 232, 233, 46, 116, 120, 116, 34, 44, 32, 91, 68, 97, 116, 101, 84, 105, 109, 101, 93, 58, 58, 85, 116, 99, 78, 111, 119, 41)
PS> # Representing `[IO.File]::WriteAllText("$PSScriptRoot\русский.txt", [DateTime]::UtcNow)` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\write.ps1", $ByteArray2)
PS> .\write.ps1

Теперь вы можете прочитать файл обратно с вашим оригинальным скриптом:

PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\asd.ps1", $ByteArray)
PS> .\asd.ps1
01/18/2019 17:13:15

Вызов обоих скриптов с помощью PowerShell Core:

PS> pwsh -Command ".\write.ps1; .\asd.ps1"
01/18/2019 17:21:02

Как видите, ваш скрипт успешно выполнен в PowerShell Core. Если вы просматриваете текущий каталог, то можете увидеть, что в нем есть и русский.txt, и �������.txt, и их содержимое совпадает с тем, что было напечатано на консоли.

На самом деле проблема не в том, чтобы делать чтение / запись файлов (кроме самого файла скрипта). Это можно продемонстрировать с помощью простого скрипта, который просто печатает коды символов строкового литерала:

PS> $ByteArray3 = [Byte[]](40, 39, 240, 243, 241, 241, 234, 232, 233, 39, 46, 71, 101, 116, 69, 110, 117, 109, 101, 114, 97, 116, 111, 114, 40, 41, 32, 124, 32, 37, 32, 84, 111, 73, 110, 116, 51, 50, 32, 36, 110, 117, 108, 108, 32, 124, 32, 37, 32, 84, 111, 83, 116, 114, 105, 110, 103, 32, 88, 52, 41, 32, 45, 106, 111, 105, 110, 32, 39, 45, 39)
PS> # Representing `('русский'.GetEnumerator() | % ToInt32 $null | % ToString X4) -join '-'` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\test.ps1", $ByteArray3)

Вызов его в Windows PowerShell даст один результат:

PS> .\test.ps1
0440-0443-0441-0441-043A-0438-0439

В то время как PowerShell Core даст другой:

PS> pwsh -Command ".\test.ps1"
FFFD-FFFD-FFFD-FFFD-FFFD-FFFD-FFFD

Одним из способов решения этой проблемы является использование UTF-8 с спецификацией, которая гарантирует, что и Windows PowerShell, и PowerShell Core будут использовать одну и ту же кодировку при чтении файлов сценариев.

Ответ написан с допущением, что [Text.Encoding]::Default.CodePage вернет 1251, как это выглядит для ОП.

, 'HexNumber') }) PS> [Text.Encoding]::UTF8.GetString($ByteArray) $Message = [IO.File]::ReadAllText("$PSScriptRoot\�������.txt", [System.Text.Encoding]::Default) $Message PS> [Text.Encoding]::GetEncoding(1251).GetString($ByteArray) $Message = [IO.File]::ReadAllText("$PSScriptRoot\русский.txt", [System.Text.Encoding]::Default) $Message PS> [Text.Encoding]::GetEncoding(1252).GetString($ByteArray) $Message = [IO.File]::ReadAllText("$PSScriptRoot\ðóññêèé.txt", [System.Text.Encoding]::Default) $Message

Важно использовать правильную кодировку при чтении файлов. И еще одна важная вещь: ваш файл скрипта использует кодовую страницу 1251, а не UTF-8. Также обратите внимание, что последовательность байтов F0-F3-F1-F1-EA-E8-E9 (которая представляет мир русский в кодовой странице 1251) является недопустимой последовательностью байтов в соответствии с UTF-8, поэтому вместо этого вы получаете семь заменяющих символов (U+FFFD).

Поскольку PowerShell Core по умолчанию использует UTF-8, а в вашем файле сценария нет спецификации для указания иного (хотя нет спецификации, которая позволяет PowerShell распознавать кодовую страницу 1251), PowerShell Core читает ваш файл с использованием кодировки UTF-8, таким образом, он пытается получить доступ к �������.txt (которого у вас нет) вместо русский.txt.

Вы можете легко наблюдать это сами, если измените сценарий на запись файла вместо чтения.

PS> $ByteArray2 = [Byte[]](91, 73, 79, 46, 70, 105, 108, 101, 93, 58, 58, 87, 114, 105, 116, 101, 65, 108, 108, 84, 101, 120, 116, 40, 34, 36, 80, 83, 83, 99, 114, 105, 112, 116, 82, 111, 111, 116, 92, 240, 243, 241, 241, 234, 232, 233, 46, 116, 120, 116, 34, 44, 32, 91, 68, 97, 116, 101, 84, 105, 109, 101, 93, 58, 58, 85, 116, 99, 78, 111, 119, 41)
PS> # Representing `[IO.File]::WriteAllText("$PSScriptRoot\русский.txt", [DateTime]::UtcNow)` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\write.ps1", $ByteArray2)
PS> .\write.ps1

Теперь вы можете прочитать файл обратно с вашим оригинальным скриптом:

PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\asd.ps1", $ByteArray)
PS> .\asd.ps1
01/18/2019 17:13:15

Вызов обоих скриптов с помощью PowerShell Core:

PS> pwsh -Command ".\write.ps1; .\asd.ps1"
01/18/2019 17:21:02

Как видите, ваш скрипт успешно выполнен в PowerShell Core. Если вы просматриваете текущий каталог, то можете увидеть, что в нем есть и русский.txt, и �������.txt, и их содержимое совпадает с тем, что было напечатано на консоли.

На самом деле проблема не в том, чтобы делать чтение / запись файлов (кроме самого файла скрипта). Это можно продемонстрировать с помощью простого скрипта, который просто печатает коды символов строкового литерала:

PS> $ByteArray3 = [Byte[]](40, 39, 240, 243, 241, 241, 234, 232, 233, 39, 46, 71, 101, 116, 69, 110, 117, 109, 101, 114, 97, 116, 111, 114, 40, 41, 32, 124, 32, 37, 32, 84, 111, 73, 110, 116, 51, 50, 32, 36, 110, 117, 108, 108, 32, 124, 32, 37, 32, 84, 111, 83, 116, 114, 105, 110, 103, 32, 88, 52, 41, 32, 45, 106, 111, 105, 110, 32, 39, 45, 39)
PS> # Representing `('русский'.GetEnumerator() | % ToInt32 $null | % ToString X4) -join '-'` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\test.ps1", $ByteArray3)

Вызов его в Windows PowerShell даст один результат:

PS> .\test.ps1
0440-0443-0441-0441-043A-0438-0439

В то время как PowerShell Core даст другой:

PS> pwsh -Command ".\test.ps1"
FFFD-FFFD-FFFD-FFFD-FFFD-FFFD-FFFD

Одним из способов решения этой проблемы является использование UTF-8 с спецификацией, которая гарантирует, что и Windows PowerShell, и PowerShell Core будут использовать одну и ту же кодировку при чтении файлов сценариев.

Ответ написан с допущением, что [Text.Encoding]::Default.CodePage вернет 1251, как это выглядит для ОП.

23
задан rae1 17 January 2013 в 21:07
поделиться

4 ответа

Для тех, кто хочет сохранить наносекундную часть даты, вам нужно использовать DateTime2 в качестве типа столбца sql, а также тип Datehime2 Nhibernate.

Вот мое соглашение по настройке (используя свободное владение)

public class DateTimeConvention : IPropertyConvention, IPropertyConventionAcceptance
{

    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => x.Type == typeof(DateTime) || x.Type == typeof(DateTime?));
    }
    public void Apply(IPropertyInstance instance)
    {
        instance.CustomSqlType("DateTime2"); //specify that the sql column is DateTime2
        instance.CustomType("DateTime2"); //set the nhib type as well
    }
}

И чтобы активировать соглашение:

 var v = Fluently.Configure()
         .Database(MsSqlConfiguration.MsSql2008
         .ConnectionString(d => d.FromConnectionStringWithKey("connstring"))
         .ShowSql())
         .Mappings(m => m.FluentMappings.AddFromAssemblyOf<IRepository>()
         .Conventions.AddFromAssemblyOf<IRepository>()) //this adds your convention
         .BuildSessionFactory();

Используя это, вы сохраните наносекунды при хранении вашего DateTimes.

5
ответ дан 29 November 2019 в 02:11
поделиться

В моем домене допустимо терять миллисекунды с даты и времени в SQL Server. Поэтому я допускаю допуск в моих тестерах устойчивости, использующих этот статический помощник (реализация nunit):

public static class AssertDateTime
{
    /// <summary>
    /// Checks that the DateTimes are no more than second apart
    /// </summary>
    /// <param name="Expected"></param>
    /// <param name="Actual"></param>
    public static void AreWithinOneSecondOfEachOther(DateTime Expected, DateTime Actual)
    {
        var timespanBetween = Actual.Subtract(Expected);

        if (timespanBetween > TimeSpan.FromSeconds(1))
            Assert.Fail(string.Format("The times were more than a second appart. They were out by {0}. Expected {1}, Actual {2}.", timespanBetween, Expected, Actual));
    }
}
0
ответ дан 29 November 2019 в 02:11
поделиться

Я столкнулся с такой же проблемой с полем аудита CreatedDate на моих бизнес-классах. Я поработал над ним, установив время, используя значение из утилитного метода. Надеюсь, это поможет.

     /// <summary>
    /// Return a DateTime with millisecond resolution to be used as the timestamp. This is needed so that DateTime of an existing instance
    /// will equal one that has been persisted and returned from the database. Without this, the times differ due to different resolutions.
    /// </summary>
    /// <returns></returns>
    private DateTime GetTime()
    {
        var now = DateTime.Now;
        var ts = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond, DateTimeKind.Local);
        return ts;
    }
1
ответ дан 29 November 2019 в 02:11
поделиться

На самом деле ссылка NHibernate утверждает, что тип DateTime nhibernate будет хранить .NET DateTime как SQL datetime, усеченный на втором уровне (без миллисекундной детализации)

Таким образом, он предоставляет тип Timestamp NHibernate ( type = «Timestamp» в сопоставлении), который будет хранить .NET DateTime как SQL datetime без усечения. Обратите внимание, что тип данных SQL timestamp не необходим и фактически прервется, если у вас будет более одного столбца timestamp в одной таблице. Таким образом, важно различать атрибуты sql-type и type в отображении NHibernate.

Кроме того, обратите внимание, что если вы работаете с фильтрами, то же правило применяется к определению фильтра: если вы укажете параметр DateTime , значение параметра будет усечено без миллисекунд.

См. главу 5.2.2. Основные типы значений , Таблица 5.3 Типы сопоставления System.ValueType .

32
ответ дан 29 November 2019 в 02:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: