[c#] Entity Framework DateTime 및 UTC

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가 데이터베이스에서 엔터티를로드 할 때마다 DateTimeKindUTC와 같이 지정한를 설정합니다.

이것은 저장할 때 아무 작업도 수행하지 않습니다. 저장을 시도하기 전에 값을 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 개체에 대해 작동하지 않습니다. 성능도 문제가 될 수 있습니다.

이를 위해서는 DbCommandInterceptorEntityFramework에서 제공하는 객체 인를 사용해야합니다 .

인터셉터 생성 :

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.UnspecifiedEF가 아닌 다른 사람이 해당 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로 처리되지 않도록 제외 하려면이 링크를 확인하십시오 .