Значение Linq to SQL DateTime является локальным (Kind = Unspecified) - Как мне настроить UTC?

Нет ли (простого) способа рассказать классам Linq To SQL, что конкретное свойство DateTime следует рассматривать как UTC (т.е. по умолчанию для свойства типа DateTime быть Utc) или существует ли ' очистить "?

Часовой пояс моего приложения-сервера не совпадает с сервером SQL Server 2005 (он не может изменить какой-либо), и ни один из них не является UTC. Когда я сохраняю свойство типа DateTime на дБ, я использую значение UTC (поэтому значение в столбце db равно UTC), но когда я читаю значения обратно (используя Linq To SQL), я получаю свойство .Kind в DateTime значение "Unspecified".

Проблема заключается в том, что когда я 'конвертирую' его в UTC, это 4 часа. Это также означает, что при его сериализации он заканчивается на стороне клиента с 4-часовым неправильным смещением (поскольку он сериализуется с использованием UTC).

Ответ 1

Сгенерированный код LinqToSql предоставляет точки расширяемости, поэтому вы можете устанавливать значения при загрузке объектов.

Ключом является создание частичного класса, который расширяет сгенерированный класс, а затем реализует частичный метод OnLoaded.

Например, пусть ваш класс Person, поэтому у вас есть сгенерированный частичный класс Person в Blah.designer.cs.

Расширьте частичный класс, создав новый класс (должен быть в другом файле) следующим образом:

public partial class Person {

  partial void OnLoaded() {
    this._BirthDate = DateTime.SpecifyKind(this._BirthDate, DateTimeKind.Utc);
  }
}

Ответ 2

SQL Server DateTime не включает информацию о часовом поясе или DateTimeKind, поэтому значения DateTime, полученные из базы данных, имеют тип = DateTimeKind.Unspecified.

Если вы хотите сделать это время UTC, вы должны "преобразовать" их следующим образом:

DateTime utcDateTime = new DateTime(databaseDateTime.Ticks, DateTimeKind.Utc);

или эквивалент:

DateTime utcDateTime = DateTime.SpecifyKind(databaseDateTime, DateTimeKind.Utc);

Я предполагаю, что ваша проблема заключается в том, что вы пытаетесь преобразовать их следующим образом:

DateTime utcDateTime = databaseDateTime.ToUniversalTime();

Это может показаться разумным с первого взгляда, но согласно документации MSDN для DateTime.ToUniversalTime при преобразовании DateTime, вид которого не указан:

Предполагается текущий объект DateTime быть местным временем, а преобразование выполняется, как если бы "Вид" был локальным.

Это поведение необходимо для обратной совместимости с .NET 1.x, у которого не было свойства DateTime.Kind.

Ответ 3

Единственный способ, который я могу сделать, это добавить свойство shim в частичный класс, который выполняет перевод...

Ответ 4

Для нашего случая было нецелесообразно всегда указывать DateTimeKind, как указано ранее:

DateTime utcDateTime = DateTime.SpecifyKind(databaseDateTime, DateTimeKind.Utc);

Мы используем Entity Framework, но это должно быть похоже на Linq-to-SQL

Если вы хотите, чтобы все объекты DateTime, выходящие из базы данных, указывались в формате UTC, вам нужно добавить файл преобразования T4 и добавить дополнительную логику для всех объектов DateTime и nullable DateTime, чтобы они инициализировались как DateTimeKind. Utc

У меня есть сообщение в блоге, которое объясняет это шаг за шагом: http://www.aaroncoleman.net/post/2011/06/16/Forcing-Entity-Framework-to-mark-DateTime-fields-at-UTC.aspx

Короче:

1) Создайте файл .tt для вашей .edmx-модели (или .dbml для Linq-to-SQL)

2) Откройте файл .tt и найдите метод WritePrimitiveTypeProperty.

3) Замените существующий код настройки. Это все между обратными вызовами метода ReportPropertyChanging и ReportPropertyChanged со следующим:

<#+ if( ((PrimitiveType)primitiveProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.DateTime)
            {
#>
        if(<#=code.FieldName(primitiveProperty)#> == new DateTime())
        {
            <#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
<#+ 
            if(ef.IsNullable(primitiveProperty))
            {  
#>              
            if(value != null)
                <#=code.FieldName(primitiveProperty)#> = DateTime.SpecifyKind(<#=code.FieldName(primitiveProperty)#>.Value, DateTimeKind.Utc);
<#+             } 
            else
            {#>
            <#=code.FieldName(primitiveProperty)#> = DateTime.SpecifyKind(<#=code.FieldName(primitiveProperty)#>, DateTimeKind.Utc);                
<#+ 
            } 
#>
        }
        else
        {
            <#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
        }
<#+ 
        }
        else
        {
#>
    <#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
<#+ 
        }
#>

Ответ 5

@rob263 предоставил отличный метод.

Это только дополнительная помощь, которую я хочу предоставить, если вы используете Entity Framework вместо Linq To Sql.

Entity Framework не поддерживает событие OnLoaded.

Вместо этого вы можете сделать следующее:

 public partial class Person
    {
        protected override void OnPropertyChanged(string property)
        {
            if (property == "BirthDate")
            {
                this._BirthDate= DateTime.SpecifyKind(this._BirthDate, DateTimeKind.Utc);
            }

            base.OnPropertyChanged(property);
        }

    }

Ответ 6

Я использую этот способ, чтобы указать DateTimeKind "на лету":

DateTime myDateTime = new DateTime(((DateTime)myUtcValueFromDb).Ticks, DateTimeKind.Utc);

Ответ 7

Этот фрагмент кода позволит вам преобразовать DateTimes (Kind = Unspecified), который вы возвращаете из LINQ to SQL в UTC, без ущерба для времени.

TimeZoneInfo UTCTimeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC");
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(myLinq2SQLTime, UTCTimeZone);

Есть, вероятно, более чистые способы сделать это, но у меня было это с рук и можно было быстро протестировать его!

Я не уверен, есть ли способ заставить его работать с классами LINQ to SQL прозрачно - вы можете захотеть посмотреть в частичном классе и посмотреть, можете ли вы зацепить, где значения читаются/записываются.

Ответ 8

Я хочу UTC, TimeZone класс может сделать это для вас, если вы хотите конвертировать между разными часовыми поясами, чем TimeZoneInfo для вас. пример моего кода с помощью TimeZoneInfo:

TimeZoneInfo cet = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
ac.add_datetime = TimeZoneInfo.ConvertTime(DateTime.Now, cet);