Entity Framework DateTime и UTC
Возможно ли иметь Entity Framework (я использую первый подход Code с CTP5 в настоящее время) хранить все значения DateTime в формате UTC в базе данных?
Или может быть способ указать его в сопоставлении, например, в этом столбце last_login:
modelBuilder.Entity<User>().Property(x => x.Id).HasColumnName("id");
modelBuilder.Entity<User>().Property(x => x.IsAdmin).HasColumnName("admin");
modelBuilder.Entity<User>().Property(x => x.IsEnabled).HasColumnName("enabled");
modelBuilder.Entity<User>().Property(x => x.PasswordHash).HasColumnName("password_hash");
modelBuilder.Entity<User>().Property(x => x.LastLogin).HasColumnName("last_login");
Ответы
Ответ 1
Вот один из вариантов, который вы могли бы рассмотреть:
Сначала определите следующий атрибут:
[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute
{
private readonly DateTimeKind _kind;
public DateTimeKindAttribute(DateTimeKind kind)
{
_kind = kind;
}
public DateTimeKind Kind
{
get { return _kind; }
}
public static void Apply(object entity)
{
if (entity == null)
return;
var properties = entity.GetType().GetProperties()
.Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?));
foreach (var property in properties)
{
var attr = property.GetCustomAttribute<DateTimeKindAttribute>();
if (attr == null)
continue;
var dt = property.PropertyType == typeof(DateTime?)
? (DateTime?) property.GetValue(entity)
: (DateTime) property.GetValue(entity);
if (dt == null)
continue;
property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind));
}
}
}
Теперь привяжите этот атрибут до вашего контекста EF:
public class MyContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public MyContext()
{
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
(sender, e) => DateTimeKindAttribute.Apply(e.Entity);
}
}
Теперь в любых свойствах DateTime
или DateTime?
вы можете применить этот атрибут:
public class Foo
{
public int Id { get; set; }
[DateTimeKind(DateTimeKind.Utc)]
public DateTime Bar { get; set; }
}
Если это произойдет, когда Entity Framework загрузит объект из базы данных, он установит указанный DateTimeKind
, например, UTC.
Обратите внимание, что при сохранении это ничего не делает. Вы все равно должны будете правильно преобразовать значение в UTC, прежде чем пытаться его сохранить. Но это позволяет вам установить тип при извлечении, который позволяет его сериализовать как UTC или преобразовать в другие часовые пояса с помощью TimeZoneInfo
.
Ответ 2
Мне очень нравится подход Мэтта Джонсона, но в моей модели ВСЕ мои члены DateTime - это UTC, и я не хочу, чтобы украшать все из них атрибутом. Поэтому я обобщил подход Мэтта, чтобы позволить обработчику события применять значение "Тип" по умолчанию, если элемент явно не украшен атрибутом.
Конструктор класса ApplicationDbContext включает этот код:
/// <summary> Constructor: Initializes a new ApplicationDbContext instance. </summary>
public ApplicationDbContext()
: base(MyApp.ConnectionString, throwIfV1Schema: false)
{
// Set the Kind property on DateTime variables retrieved from the database
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
(sender, e) => DateTimeKindAttribute.Apply(e.Entity, DateTimeKind.Utc);
}
DateTimeKindAttribute
выглядит следующим образом:
/// <summary> Sets the DateTime.Kind value on DateTime and DateTime? members retrieved by Entity Framework. Sets Kind to DateTimeKind.Utc by default. </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute
{
/// <summary> The DateTime.Kind value to set into the returned value. </summary>
public readonly DateTimeKind Kind;
/// <summary> Specifies the DateTime.Kind value to set on the returned DateTime value. </summary>
/// <param name="kind"> The DateTime.Kind value to set on the returned DateTime value. </param>
public DateTimeKindAttribute(DateTimeKind kind)
{
Kind = kind;
}
/// <summary> Event handler to connect to the ObjectContext.ObjectMaterialized event. </summary>
/// <param name="entity"> The entity (POCO class) being materialized. </param>
/// <param name="defaultKind"> [Optional] The Kind property to set on all DateTime objects by default. </param>
public static void Apply(object entity, DateTimeKind? defaultKind = null)
{
if (entity == null) return;
// Get the PropertyInfos for all of the DateTime and DateTime? properties on the entity
var properties = entity.GetType().GetProperties()
.Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?));
// For each DateTime or DateTime? property on the entity...
foreach (var propInfo in properties) {
// Initialization
var kind = defaultKind;
// Get the kind value from the [DateTimekind] attribute if it present
var kindAttr = propInfo.GetCustomAttribute<DateTimeKindAttribute>();
if (kindAttr != null) kind = kindAttr.Kind;
// Set the Kind property
if (kind != null) {
var dt = (propInfo.PropertyType == typeof(DateTime?))
? (DateTime?)propInfo.GetValue(entity)
: (DateTime)propInfo.GetValue(entity);
if (dt != null) propInfo.SetValue(entity, DateTime.SpecifyKind(dt.Value, kind.Value));
}
}
}
}
Ответ 3
Здесь хорошая статья в блоге по теме:
http://www.aaroncoleman.net/post/2011/06/16/Forcing-Entity-Framework-to-mark-DateTime-fields-at-UTC.aspx
Ответ 4
Если вы тщательно пропустите даты UTC, когда вы устанавливаете значения и все, о чем вы заботитесь, убедитесь, что DateTimeKind правильно настроен, когда сущности извлекаются из базы данных, см. мой ответ здесь: fooobar.com/questions/87236/...
Ответ 5
Я изучаю это прямо сейчас, и большинство из этих ответов не совсем велики. Из того, что я вижу, нет способа сказать EF6, что даты, выходящие из базы данных, находятся в формате UTC. Если это так, самый простой способ убедиться, что ваши свойства DateTime модели находятся в UTC, это проверить и преобразовать в установщик.
Здесь некоторый С#, такой как псевдокод, который описывает алгоритм
public DateTime MyUtcDateTime
{
get
{
return _myUtcDateTime;
}
set
{
if(value.Kind == DateTimeKind.Utc)
_myUtcDateTime = value;
else if (value.Kind == DateTimeKind.Local)
_myUtcDateTime = value.ToUniversalTime();
else
_myUtcDateTime = DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
}
Первые две ветки очевидны. Последнее держит секретный соус.
Когда EF6 создает модель из данных, загружаемых из базы данных, DateTimes DateTimeKind.Unspecified
. Если вы знаете, что ваши даты - это все UTC в db, то последняя ветка отлично подойдет вам.
DateTime.Now
всегда DateTimeKind.Local
, поэтому приведенный выше алгоритм отлично работает для дат, сгенерированных в коде. Большую часть времени.
Однако вы должны быть осторожны, так как есть другие способы, которыми DateTimeKind.Unspecified
может проникнуть в ваш код. Например, вы можете десериализировать свои модели из данных JSON, и ваш десериализатор по умолчанию подходит для этого типа. Это вам, чтобы защитить от локализованных дат, отмеченных DateTimeKind.Unknown
, от попадания в этот сеттер от кого-либо, кроме EF.
Ответ 6
Я считаю, что нашел решение, которое не требует какой-либо пользовательской проверки UTC или манипуляции с DateTime.
В основном вам нужно изменить свои объекты EF для использования типа данных DateTimeOffset (NOT DateTime). Это сохранит часовой пояс со значением даты в базе данных (SQL Server 2015 в моем случае).
Когда EF Core запрашивает данные из БД, он также получит информацию о часовом поясе.
Когда вы передаете эти данные в веб-приложение (Angular2 в моем случае), дата автоматически преобразуется в локальный часовой пояс браузера, что я ожидаю.
И когда он передается обратно на мой сервер, он автоматически преобразуется в UTC, также как и ожидалось.
Ответ 7
Невозможно указать DataTimeKind в Entity Framework. Вы можете преобразовать значения времени даты в utc перед сохранением в db и всегда принимать данные, извлеченные из db как UTC. Но объекты DateTime, обеспеченные во время запроса, всегда будут "Unspecified". Вы также можете evalualte использовать объект DateTimeOffset вместо DateTime.
Ответ 8
Для тех, кто нуждается в решении @MattJohnson с .net framework 4, как я, с синтаксисом/ограничением рефлекса, для этого требуется небольшая модификация, как указано ниже:
foreach (var property in properties)
{
DateTimeKindAttribute attr = (DateTimeKindAttribute) Attribute.GetCustomAttribute(property, typeof(DateTimeKindAttribute));
if (attr == null)
continue;
var dt = property.PropertyType == typeof(DateTime?)
? (DateTime?)property.GetValue(entity,null)
: (DateTime)property.GetValue(entity, null);
if (dt == null)
continue;
//If the value is not null set the appropriate DateTimeKind;
property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind) ,null);
}
Ответ 9
Другим подходом было бы создать интерфейс с свойствами datetime, реализовать их на частичных сущностных классах. А затем используйте событие SavingChanges, чтобы проверить, имеет ли объект тип интерфейса, установите эти значения даты и времени для того, что вы хотите. Фактически, если они созданы/изменены в виде дат, вы можете использовать это событие для их заполнения.
Ответ 10
В моем случае у меня была только одна таблица с датами времени UTC. Вот что я сделал:
public partial class MyEntity
{
protected override void OnPropertyChanged(string property)
{
base.OnPropertyChanged(property);
// ensure that values coming from database are set as UTC
// watch out for property name changes!
switch (property)
{
case "TransferDeadlineUTC":
if (TransferDeadlineUTC.Kind == DateTimeKind.Unspecified)
TransferDeadlineUTC = DateTime.SpecifyKind(TransferDeadlineUTC, DateTimeKind.Utc);
break;
case "ProcessingDeadlineUTC":
if (ProcessingDeadlineUTC.Kind == DateTimeKind.Unspecified)
ProcessingDeadlineUTC = DateTime.SpecifyKind(ProcessingDeadlineUTC, DateTimeKind.Utc);
default:
break;
}
}
}
Ответ 11
[EntityFramework 6]
Принятый ответ не работает для проецируемого или анонимного объекта. Производительность также может быть проблемой.
Для этого нам нужно использовать перехват, предоставляемый EntityFramework.
Создать перехватчик:
public class UtcInterceptor : DbCommandInterceptor
{
public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
base.ReaderExecuted(command, interceptionContext);
if (interceptionContext?.Result != null && !(interceptionContext.Result is UtcDbDataReader))
{
interceptionContext.Result = new UtcDbDataReader(interceptionContext.Result);
}
}
}
interceptionContext.Result - это DbDataReader, который мы заменяем нашим
public class UtcDbDataReader : DbDataReader
{
private readonly DbDataReader source;
public UtcDbDataReader(DbDataReader source)
{
this.source = source;
}
public override DateTime GetDateTime(int ordinal)
{
return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc);
}
// you need to fill all overrides, just call same method on source in all cases
public new void Dispose()
{
source.Dispose();
}
public new IDataReader GetData(int ordinal)
{
return source.GetData(ordinal);
}
}
Теперь зарегистрируйте перехватчик по конфигурации
internal class MyDbConfiguration : DbConfiguration
{
protected internal MyDbConfiguration ()
{
AddInterceptor(new UtcInterceptor());
}
}
И зарегистрируйте конфигурацию для DbContext
[DbConfigurationType(typeof(MyDbConfiguration ))]
internal class MyDbContext : DbContext
{
// ...
}
Вот оно. Приветствия.
Если вы ленивы, у меня есть вся моя реализация DbReader:
using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MyNameSpace
{
/// <inheritdoc />
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010:CollectionsShouldImplementGenericInterface")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
public class UtcDbDataReader : DbDataReader
{
private readonly DbDataReader source;
public UtcDbDataReader(DbDataReader source)
{
this.source = source;
}
/// <inheritdoc />
public override int VisibleFieldCount => source.VisibleFieldCount;
/// <inheritdoc />
public override int Depth => source.Depth;
/// <inheritdoc />
public override int FieldCount => source.FieldCount;
/// <inheritdoc />
public override bool HasRows => source.HasRows;
/// <inheritdoc />
public override bool IsClosed => source.IsClosed;
/// <inheritdoc />
public override int RecordsAffected => source.RecordsAffected;
/// <inheritdoc />
public override object this[string name] => source[name];
/// <inheritdoc />
public override object this[int ordinal] => source[ordinal];
/// <inheritdoc />
public override bool GetBoolean(int ordinal)
{
return source.GetBoolean(ordinal);
}
/// <inheritdoc />
public override byte GetByte(int ordinal)
{
return source.GetByte(ordinal);
}
/// <inheritdoc />
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
{
return source.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
}
/// <inheritdoc />
public override char GetChar(int ordinal)
{
return source.GetChar(ordinal);
}
/// <inheritdoc />
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
{
return source.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
}
/// <inheritdoc />
public override string GetDataTypeName(int ordinal)
{
return source.GetDataTypeName(ordinal);
}
/// <summary>
/// Returns datetime with Utc kind
/// </summary>
public override DateTime GetDateTime(int ordinal)
{
return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc);
}
/// <inheritdoc />
public override decimal GetDecimal(int ordinal)
{
return source.GetDecimal(ordinal);
}
/// <inheritdoc />
public override double GetDouble(int ordinal)
{
return source.GetDouble(ordinal);
}
/// <inheritdoc />
public override IEnumerator GetEnumerator()
{
return source.GetEnumerator();
}
/// <inheritdoc />
public override Type GetFieldType(int ordinal)
{
return source.GetFieldType(ordinal);
}
/// <inheritdoc />
public override float GetFloat(int ordinal)
{
return source.GetFloat(ordinal);
}
/// <inheritdoc />
public override Guid GetGuid(int ordinal)
{
return source.GetGuid(ordinal);
}
/// <inheritdoc />
public override short GetInt16(int ordinal)
{
return source.GetInt16(ordinal);
}
/// <inheritdoc />
public override int GetInt32(int ordinal)
{
return source.GetInt32(ordinal);
}
/// <inheritdoc />
public override long GetInt64(int ordinal)
{
return source.GetInt64(ordinal);
}
/// <inheritdoc />
public override string GetName(int ordinal)
{
return source.GetName(ordinal);
}
/// <inheritdoc />
public override int GetOrdinal(string name)
{
return source.GetOrdinal(name);
}
/// <inheritdoc />
public override string GetString(int ordinal)
{
return source.GetString(ordinal);
}
/// <inheritdoc />
public override object GetValue(int ordinal)
{
return source.GetValue(ordinal);
}
/// <inheritdoc />
public override int GetValues(object[] values)
{
return source.GetValues(values);
}
/// <inheritdoc />
public override bool IsDBNull(int ordinal)
{
return source.IsDBNull(ordinal);
}
/// <inheritdoc />
public override bool NextResult()
{
return source.NextResult();
}
/// <inheritdoc />
public override bool Read()
{
return source.Read();
}
/// <inheritdoc />
public override void Close()
{
source.Close();
}
/// <inheritdoc />
public override T GetFieldValue<T>(int ordinal)
{
return source.GetFieldValue<T>(ordinal);
}
/// <inheritdoc />
public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken)
{
return source.GetFieldValueAsync<T>(ordinal, cancellationToken);
}
/// <inheritdoc />
public override Type GetProviderSpecificFieldType(int ordinal)
{
return source.GetProviderSpecificFieldType(ordinal);
}
/// <inheritdoc />
public override object GetProviderSpecificValue(int ordinal)
{
return source.GetProviderSpecificValue(ordinal);
}
/// <inheritdoc />
public override int GetProviderSpecificValues(object[] values)
{
return source.GetProviderSpecificValues(values);
}
/// <inheritdoc />
public override DataTable GetSchemaTable()
{
return source.GetSchemaTable();
}
/// <inheritdoc />
public override Stream GetStream(int ordinal)
{
return source.GetStream(ordinal);
}
/// <inheritdoc />
public override TextReader GetTextReader(int ordinal)
{
return source.GetTextReader(ordinal);
}
/// <inheritdoc />
public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken)
{
return source.IsDBNullAsync(ordinal, cancellationToken);
}
/// <inheritdoc />
public override Task<bool> ReadAsync(CancellationToken cancellationToken)
{
return source.ReadAsync(cancellationToken);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
public new void Dispose()
{
source.Dispose();
}
public new IDataReader GetData(int ordinal)
{
return source.GetData(ordinal);
}
}
}