2009-05-05 11 views
56

¿No hay una forma (simple) de indicar a las clases de SQL de Linq que una propiedad particular de DateTime se debe considerar como UTC (es decir, que tiene la propiedad Kind del tipo DateTime para ser Utc por defecto), o hay un 'clean 'solución alternativa?Linq to SQL Los valores de DateTime son locales (tipo = sin especificar) - ¿Cómo puedo hacer que sea UTC?

La zona horaria en mi servidor de aplicaciones no es la misma que la del servidor SQL 2005 (no puede cambiar ninguna), y ninguna es UTC. Cuando persisto una propiedad de tipo DateTime en el dB, uso el valor UTC (por lo que el valor en la columna db es UTC), pero cuando leo los valores nuevamente (usando Linq To SQL) obtengo la propiedad .Kind del DateTime valor para ser 'No especificado'.

El problema es que cuando lo "convierto" a UTC es de 4 horas. Esto también significa que cuando se serializa, termina en el lado del cliente con un desplazamiento incorrecto de 4 horas (ya que se serializa usando el UTC).

Respuesta

5

La única manera que puedo pensar para hacer esto sería añadir una propiedad para nivelar en una clase parcial que hace la traducción ...

-1

Este fragmento de código le permitirá convertir los DateTime (Tipo = sin especificar) regresas de LINQ a SQL en horarios UTC sin que los tiempos se vean afectados.

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

¡Probablemente haya formas más limpias de hacerlo, pero tenía esto a mano y podría probarlo rápidamente!

No estoy seguro de si hay una manera de hacerlo funcionar de forma transparente con las clases de LINQ a SQL; es posible que desee buscar en la clase parcial y ver si puede enganchar donde se leen/escriben los valores.

+0

Dado que UTC es una zona horaria conocido, es más rápido usar DateTime.SpecifyKind. Si se sabe que TimeZone no es el TimeZone local actual ni el UTC (por ejemplo, servidores DB en EST y servidores de aplicaciones en PST), ese sería el único momento en el que preferiría utilizar TimeZoneInfo. –

-4

Si desea UTC, la clase TimeZone puede hacerlo por usted, si desea convertir entre diferentes zonas horarias, entonces TimeZoneInfo es para usted. exemple de mi código con TimeZoneInfo:

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

DateTime de SQL Server no incluye ninguna zona horaria o información DateTimeKind, por lo tanto, los valores de DateTime recuperados de la base de datos correctamente tener Kind = DateTimeKind.Unspecified.

Si desea realizar estos tiempos UTC, debe 'convertir' de la siguiente manera:

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

o el equivalente:

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

Asumo que su problema es que está intentando convertirlos de la siguiente manera:

DateTime utcDateTime = databaseDateTime.ToUniversalTime(); 

Esto puede parecer razonable a primera vista, pero de acuerdo con the MSDN documentation for DateTime.ToUniversalTime, al convertir un DateTime cuyo tipo es por:

El objeto DateTime actual se supone ser una hora local, y la conversión se realiza como si Kind eran Local.

Este comportamiento es necesario para la compatibilidad con versiones anteriores de .NET 1.x, que no tenía una propiedad DateTime.Kind.

+1

Esta es una buena solución, aunque la respuesta sobre 'partial void OnLoaded' permite una mejor solución – vdboor

+0

DateTime.SpecifyKind usa los Ticks bajo el capó, dado que ha estado presente desde .NET 2, yo preferiría ese. –

36

El código LinqToSql generado proporciona puntos de extensibilidad, por lo que puede establecer valores cuando se cargan los objetos.

La clave es crear una clase parcial que amplíe la clase generada, y luego implementar el método parcial OnLoaded.

Por ejemplo, supongamos que su clase es Person, por lo que tiene una clase Person parcial generada en Blah.designer.cs.

extender la clase parcial mediante la creación de una nueva clase (debe estar en un archivo diferente), como sigue:

public partial class Person { 

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

¡Impresionante! esto me permitió hacer una buena solución! – vdboor

+1

Desafortunadamente, las clases de resultados del procedimiento almacenado L2S no parecen ofrecer esta extensibilidad :(Supongo que haré que la función SP sea privada y cree una sobrecarga pública que especifique el tipo. –

3

@ rob263 proporciona un método excelente.

Esto es solo una ayuda adicional que deseo proporcionar si está utilizando Entity Framework en lugar de Linq To Sql.

Entity Framework no admite el evento OnLoaded.

su lugar, puede hacer lo siguiente:

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

      base.OnPropertyChanged(property); 
     } 

    } 
4

Para nuestro caso no era práctico siempre especificar el DateTimeKind como se dijo anteriormente:

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

Estamos utilizando Entity Framework, pero esto debe ser similar a Linq-to-SQL

Si desea forzar que todos los objetos DateTime que salen de la base de datos se especifiquen como UTC necesitará agregar un archivo de transformación T4 y agregar lógica adicional para todos los objetos DateTime y anulable DateTime de tal manera que son inicializadas como DateTimeKind.Utc

que tienen un blog que explica paso a paso: http://www.aaroncoleman.net/post/2011/06/16/Forcing-Entity-Framework-to-mark-DateTime-fields-at-UTC.aspx

En resumen:

1) Crear el archivo .tt para su modelo .edmx (o .dbml para Linq-to-SQL)

2) Abra el archivo .tt y encuentre el método "WritePrimitiveTypeProperty".

3) Reemplace el código del colocador existente. Esto es todo lo que entre el ReportPropertyChanging y los ReportPropertyChanged devoluciones de llamada de método con lo siguiente:

<#+ 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)#>); 
<#+ 
     } 
#> 
+0

Este enfoque es genial, pero estoy tratando de encontrar la solución para este mismo escenario, excepto cuando usa proyección para crear objetos nuevos en una consulta LINQ. Por ejemplo, 'context.Entities.Select (o => new {Date = o.DateField})' –

+0

@Mauricio, si está utilizando un archivo .edmx, debe ser similar o lo mismo. La idea es que solo está utilizando la transformación T4 para anular cómo el marco inicializa DateTimes cuando se proyecta. – aarondcoleman

+0

¡Gracias! De hecho, le di una oportunidad (en aquel entonces) y, por lo que recuerdo, pude anular la proyección Entidades pero no los tipos primitivos. Es posible que tenga que regresar y comprobar si todavía cree que los tipos primitivos pueden destrozarse durante la inicialización de la proyección. –

0

utilizo este modo para especificar el DateTimeKind sobre la marcha:

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

DateTime.SpecifyKind usa los Ticks debajo del capó, dado que ha estado presente desde .NET 2, yo preferiría ese. –