web-dev-qa-db-fra.com

Les valeurs Linq to SQL DateTime sont locales (Kind = Unspecified) - Comment puis-je le convertir en UTC?

N'y a-t-il pas un moyen (simple) d'indiquer aux classes Linq To SQL qu'une propriété DateTime particulière doit être considérée comme UTC (c'est-à-dire que la propriété Kind du type DateTime est Utc par défaut), ou existe-t-il une solution de contournement 'propre' ?

Le fuseau horaire de mon serveur d'applications n'est pas le même que celui de SQL 2005 Server (aucun ne peut en changer), et aucun n'est UTC. Lorsque je persiste dans le dB avec une propriété de type DateTime, j'utilise la valeur UTC (la valeur dans la colonne db est donc UTC), mais lorsque je relis les valeurs (à l'aide de Linq To SQL), j'obtiens la propriété .Kind de DateTime. valeur à 'Non spécifiée'.

Le problème est que lorsque je le convertis en UTC, il ne reste que 4 heures. Cela signifie également que lorsqu'il est sérialisé, il se retrouve côté client avec un décalage erroné de 4 heures (car il est sérialisé à l'aide de l'UTC).

58
ericsson007

Le code LinqToSql généré fournit des points d'extensibilité, ce qui vous permet de définir des valeurs lorsque les objets sont chargés.

La clé consiste à créer une classe partielle qui étend la classe générée, puis à implémenter la méthode partielle OnLoaded.

Par exemple, supposons que votre classe est Person, vous avez donc une classe Person partielle générée dans Blah.designer.cs.

Étendez la classe partielle en créant une nouvelle classe (doit être dans un fichier différent), comme suit:

public partial class Person {

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

SQL Server DateTime n'inclut pas de fuseau horaire ni d'informations DateTimeKind. Par conséquent, les valeurs DateTime extraites de la base de données ont correctement Kind = DateTimeKind.Unspecified.

Si vous voulez faire ces heures UTC, vous devriez les "convertir" comme suit:

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

ou l'équivalent:

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

Je suppose que votre problème est que vous essayez de les convertir comme suit:

DateTime utcDateTime = databaseDateTime.ToUniversalTime();

Cela peut sembler raisonnable au premier abord, mais selon la documentation MSDN de DateTime.ToUniversalTime , lors de la conversion d'un DateTime dont le type est non spécifié:

L'objet DateTime actuel est supposé que Est une heure locale et la conversion Est effectuée comme si Kind était local.

Ce comportement est nécessaire pour assurer la compatibilité ascendante avec .NET 1.x, qui ne possédait pas de propriété DateTime.Kind.

28
Joe

La seule façon pour moi de faire cela serait d’ajouter une propriété shim dans une classe partielle qui effectue la traduction ...

5
Marc Gravell

Dans notre cas, il n’était pas pratique de toujours spécifier le DateTimeKind comme indiqué précédemment:

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

Nous utilisons Entity Framework, mais cela devrait être similaire à Linq-to-SQL

Si vous souhaitez forcer la spécification UTC de tous les objets DateTime sortant de la base de données, vous devez ajouter un fichier de transformation T4 et ajouter une logique supplémentaire pour tous les objets DateTime et DateTime nullable, de sorte qu'ils soient initialisés en tant que DateTimeKind.Utc.

J'ai un article de blog qui explique cette étape par étape: http://www.aaroncoleman.net/post/2011/06/16/Forcing-Entity-Framework-to-mark-DateTime-fields-at-UTC. aspx

En bref:

1) Créez le fichier .tt pour votre modèle .edmx (ou .dbml pour Linq-to-SQL)

2) Ouvrez le fichier .tt et trouvez la méthode "WritePrimitiveTypeProperty".

3) Remplacez le code de poseur existant. C'est tout ce qui se situe entre les rappels de méthode ReportPropertyChanging et ReportPropertyChanged avec les éléments suivants:

<#+ 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
aarondcoleman

@ rob263 a fourni une excellente méthode.

C’est seulement une aide supplémentaire que je souhaite vous fournir si vous utilisez Entity Framework au lieu de Linq To Sql.

Entity Framework ne prend pas en charge l'événement OnLoaded.

Au lieu de cela, vous pouvez effectuer les opérations suivantes:

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

            base.OnPropertyChanged(property);
        }

    }
3
Gautam Jain

J'utilise cette façon de spécifier le DateTimeKind à la volée:

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