Entity Framework (현재 CTP5와 함께 Code First Approach를 사용하고 있음)가 데이터베이스에 모든 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");
답변
다음은 고려할 수있는 한 가지 접근 방식입니다.
먼저 다음 속성을 정의하십시오.
[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로 직렬화하거나 .NET Framework를 사용하여 다른 시간대로 변환 할 수 있습니다 TimeZoneInfo
.
답변
저는 Matt Johnson의 접근 방식을 정말 좋아하지만, 제 모델에서는 모든 DateTime 멤버가 UTC이고 모든 멤버를 속성으로 장식하고 싶지 않습니다. 그래서 멤버가 속성으로 명시 적으로 장식되지 않는 한 이벤트 핸들러가 기본 종류 값을 적용 할 수 있도록 Matt의 접근 방식을 일반화했습니다.
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's 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));
}
}
}
}
답변
이 답변은 Entity Framework 6에서 작동합니다.
수락 된 답변은 Projected 또는 Anonymous 개체에 대해 작동하지 않습니다. 성능도 문제가 될 수 있습니다.
이를 위해서는 DbCommandInterceptor
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 the same method on source in all cases
public new void Dispose()
{
source.Dispose();
}
public new IDataReader GetData(int ordinal)
{
return source.GetData(ordinal);
}
}
인터셉터를 귀하의 DbConfiguration
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);
}
}
}
답변
사용자 지정 UTC 검사 또는 DateTime 조작이 필요하지 않은 솔루션을 찾았다 고 생각합니다.
기본적으로 DateTimeOffset (DateTime 아님) 데이터 형식을 사용하려면 EF 엔터티를 변경해야합니다. 이것은 데이터베이스의 날짜 값과 함께 표준 시간대를 저장합니다 (제 경우에는 SQL Server 2015).
EF Core가 DB에서 데이터를 요청할 때 시간대 정보도 수신합니다. 이 데이터를 웹 애플리케이션 (제 경우에는 Angular2)에 전달하면 날짜가 자동으로 브라우저의 현지 시간대로 변환됩니다.
그리고 그것이 내 서버로 다시 전달되면 예상대로 자동으로 다시 UTC로 변환됩니다.
답변
Entity Framework에서 DataTimeKind를 지정하는 방법은 없습니다. db에 저장하기 전에 날짜 시간 값을 utc로 변환하고 항상 db에서 검색된 데이터를 UTC로 가정 할 수 있습니다. 그러나 쿼리 중에 결합 된 DateTime 개체는 항상 “Unspecified”입니다. DateTime 대신 DateTimeOffset 개체를 사용하여 평가할 수도 있습니다.
답변
나는 지금 이것을 조사하고 있으며 이러한 답변의 대부분은 정확히 훌륭하지 않습니다. 내가 볼 수 있듯이 EF6에 데이터베이스에서 나오는 날짜가 UTC 형식임을 알 수있는 방법이 없습니다. 이 경우 모델의 DateTime 속성이 UTC로되어 있는지 확인하는 가장 간단한 방법은 setter에서 확인하고 변환하는 것입니다.
알고리즘을 설명하는 의사 코드와 같은 C #이 있습니다.
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
. 날짜가 모두 db에서 UTC라는 것을 알고 있다면 마지막 분기가 잘 작동합니다.
DateTime.Now
항상 DateTimeKind.Local
이므로 위의 알고리즘은 코드에서 생성 된 날짜에 대해 잘 작동합니다. 대부분.
그러나 다른 방법 DateTimeKind.Unspecified
으로 코드에 침투 할 수 있으므로주의 해야합니다. 예를 들어 JSON 데이터에서 모델을 역 직렬화 할 수 있으며 deserializer 플레이버는 기본적으로이 종류로 설정됩니다. DateTimeKind.Unspecified
EF가 아닌 다른 사람이 해당 setter에 도달하지 못하도록 표시된 현지화 된 날짜를 방지하는 것은 사용자에게 달려 있습니다 .
답변
들어 EF 코어 , GitHub의에서이 주제에 대한 좋은 논의가 : https://github.com/dotnet/efcore/issues/4711
모든 날짜를 데이터베이스에 저장 / 검색 할 때 모든 날짜를 UTC로 처리 하는 솔루션 ( Christopher Haws에 대한 크레딧 )은 클래스 OnModelCreating
메서드에 다음을 추가하는 것 DbContext
입니다.
var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
v => v.ToUniversalTime(),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
v => v.HasValue ? v.Value.ToUniversalTime() : v,
v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v);
foreach (var entityType in builder.Model.GetEntityTypes())
{
if (entityType.IsQueryType)
{
continue;
}
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(DateTime))
{
property.SetValueConverter(dateTimeConverter);
}
else if (property.ClrType == typeof(DateTime?))
{
property.SetValueConverter(nullableDateTimeConverter);
}
}
}
또한 일부 엔터티의 일부 속성이 UTC로 처리되지 않도록 제외 하려면이 링크를 확인하십시오 .
