Entity Framework DateTime 및 UTC


96

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");

답변:


144

다음은 고려할 수있는 한 가지 접근 방식입니다.

먼저 다음 속성을 정의하십시오.

[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.


7
이 작업을 수행 할 수 없다면 다음 사용 중 하나가 누락되었을 수 있습니다. using System; System.Collections.Generic 사용; System.ComponentModel.DataAnnotations.Schema 사용; System.Linq 사용; System.Reflection 사용;
Saustrup 2014 년

7
@Saustrup-SO에 대한 대부분의 예제는 질문과 직접 ​​관련이없는 한 간결함을 위해 사용을 생략합니다. 하지만 감사합니다.
Matt Johnson-Pint

4
@MattJohnson는 Saustrup의 using 문 @, 당신은 같은 일부 인정 컴파일 에러가 발생하지 않고'System.Array' does not contain a definition for 'Where'
야곱 에거

7
@SilverSideDown이 말했듯이 이것은 .NET 4.5에서만 작동합니다. gist.github.com/munr/3544bd7fab6615290561 에서 .NET 4.0과 호환되도록 몇 가지 확장을 만들었습니다 . 주목해야 할 또 다른 점은 프로젝션에서는 작동하지 않고 완전히로드 된 엔티티에서만 작동한다는 것입니다.
Mun

5
프로젝션과 함께 진행하는 것에 대한 제안이 있습니까?
Jafin 2015-04-27

32

저는 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));
            }
        }
    }
}

1
이것은 허용되는 답변에 대한 매우 유용한 확장입니다!
학습자

아마도 내가 뭔가를 놓치고 있지만 이것이 DateTimeKind.Unspecified와 반대로 DateTimeKind.Utc로 기본 설정되는 방법은 무엇입니까?
Rhonage

1
@Rhonage 죄송합니다. 기본값은 ApplicationDbContext 생성자에서 설정됩니다. 나는 그것을 포함하도록 답변을 업데이트했습니다.
Bob.at.Indigo.Health

1
@ Bob.at.AIPsychLab 감사합니다 친구, 이제 훨씬 명확합니다. 어떤 무게 반사가 진행되고 있는지 알아 내려고했지만, 아니, 간단하다!
Rhonage

모델 DateTIme에 (공용) setter 메서드가없는 속성 이 있으면 실패합니다 . 제안 수정. 참조 stackoverflow.com/a/3762475/2279059
Florian Winter

13

이 답변은 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);
        }
    }
}

지금까지 이것이 가장 좋은 대답 인 것 같습니다. 덜 도달 한 것처럼 보였기 때문에 먼저 속성 변형을 시도했지만 생성자 이벤트 타이 인이 OnModelCreating 이벤트에서 발생하는 테이블 매핑에 대해 알지 못하는 것 같아서 내 단위 테스트가 조롱으로 실패합니다. 이것은 내 투표를 얻는다!
The Senator

1
왜 섀도 잉 Dispose하고 GetData?
user247702

2
이 코드는 아마도 @IvanStoev : stackoverflow.com/a/40349051/90287
Rami A.

당신은 공간 데이터를 매핑하는 경우 불행하게도이 실패
크리스

@ user247702 yea shadowing Dispose는 실수입니다. Dispose (bool) 재정의
user2397863

9

사용자 지정 UTC 검사 또는 DateTime 조작이 필요하지 않은 솔루션을 찾았다 고 생각합니다.

기본적으로 DateTimeOffset (DateTime 아님) 데이터 형식을 사용하려면 EF 엔터티를 변경해야합니다. 이것은 데이터베이스의 날짜 값과 함께 표준 시간대를 저장합니다 (제 경우에는 SQL Server 2015).

EF Core가 DB에서 데이터를 요청할 때 시간대 정보도 수신합니다. 이 데이터를 웹 애플리케이션 (제 경우에는 Angular2)에 전달하면 날짜가 자동으로 브라우저의 현지 시간대로 변환됩니다.

그리고 그것이 내 서버로 다시 전달되면 예상대로 자동으로 다시 UTC로 변환됩니다.


7
DateTimeOffset은 일반적인 인식과 달리 시간대를 저장하지 않습니다. 값이 나타내는 UTC의 오프셋을 저장합니다. 오프셋이 생성 된 실제 시간대를 결정하기 위해 오프셋을 역으로 매핑 할 수 없으므로 데이터 유형이 거의 쓸모 없게됩니다.
Suncat2000 2017

2
아니,하지만 정확하게 날짜 시간을 저장하는 데 사용할 수 있습니다 medium.com/@ojb500/in-praise-of-datetimeoffset-e0711f991cba

1
UTC 만 위치가 필요하지 않습니다. 모든 곳이 동일하기 때문입니다. UTC 이외의 것을 사용하는 경우 위치도 필요합니다. 그렇지 않으면 datetimeoffset을 사용할 때도 시간 정보가 쓸모가 없습니다.
Horitsu

@ Suncat2000 시점을 저장하는 가장 현명한 방법입니다. 다른 모든 날짜 / 시간 유형도 시간대를 제공하지 않습니다.
John

1
DATETIMEOFFSET은 원본 포스터가 원하는대로 수행합니다. (명시 적) 변환을 수행하지 않고 날짜-시간을 UTC로 저장합니다. @Carl DATETIME, DATETIME2 및 DATETIMEOFFSET은 모두 날짜-시간 값을 올바르게 저장합니다. UTC에서 오프셋을 추가로 저장하는 것 외에 DATETIMEOFFSET는 거의 이점이 없습니다. 데이터베이스에서 사용하는 것은 귀하의 부름입니다. 나는 많은 사람들이 잘못 생각하는 시간대저장하지 않는다는 점을 집으로 데려 가고 싶었습니다 .
Suncat2000

5

Entity Framework에서 DataTimeKind를 지정하는 방법은 없습니다. db에 저장하기 전에 날짜 시간 값을 utc로 변환하고 항상 db에서 검색된 데이터를 UTC로 가정 할 수 있습니다. 그러나 쿼리 중에 결합 된 DateTime 개체는 항상 "Unspecified"입니다. DateTime 대신 DateTimeOffset 개체를 사용하여 평가할 수도 있습니다.


5

나는 지금 이것을 조사하고 있으며 이러한 답변의 대부분은 정확히 훌륭하지 않습니다. 내가 볼 수 있듯이 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에 도달하지 못하도록 표시된 현지화 된 날짜를 방지하는 것은 사용자에게 달려 있습니다 .


6
이 문제로 몇 년 동안 씨름 한 후 알아 낸 것처럼 DateTime 필드를 다른 구조 (예 : 데이터 전송 개체)에 할당하거나 선택하는 경우 EF는 getter 및 setter 메서드를 모두 무시합니다. 이러한 경우에도 DateTimeKind.Utc결과가 생성 된 후에도 종류를로 변경 해야합니다. 예 : from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp };모든 종류를 DateTimeKind.Unspecified.
Suncat2000 2017

1
한동안 Entity Framework와 함께 DateTimeOffset을 사용해 왔으며 DateTimeOffset의 데이터 형식으로 EF 엔터티를 지정하면 모든 EF 쿼리가 DB에 저장된 것과 똑같이 UTC의 오프셋을 사용하여 날짜를 반환합니다. 따라서 데이터 유형을 DateTime 대신 DateTimeOffset으로 변경 한 경우 위의 해결 방법이 필요하지 않습니다.
Moutono

알아서 반가워요! 감사합니다 @Moutono

@ Suncat2000 코멘트에 따르면 이것은 단순히 작동하지 않으며 제거해야합니다
Ben Morris

5

들어 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로 처리되지 않도록 제외 하려면이 링크를 확인하십시오 .


저에게 가장 좋은 솔루션입니다! 감사합니다
Ben Morris

DateTimeOffset과 함께 작동합니까?
Mark Redman

1
@MarkRedman DateTimeOffset에 대한 합법적 인 사용 사례가 있다면 시간대에 대한 정보도 유지하고 싶기 때문에 말이되지 않는다고 생각합니다. DateTime과 DateTimeOffset 중에서 선택할시기는 docs.microsoft.com/en-us/dotnet/standard/datetime/… 또는 stackoverflow.com/a/14268167/3979621 을 참조하세요 .
Honza Kalfus

IsQueryType대체 된 것 같습니다 IsKeyLess: github.com/dotnet/efcore/commit/…
Mark Tielemans

4

값을 설정할 때 UTC 날짜를 올바르게 전달하도록주의하고 데이터베이스에서 엔터티를 검색 할 때 DateTimeKind가 올바르게 설정되었는지 확인하는 것이 중요하다면 여기에서 내 대답을 참조하십시오. https://stackoverflow.com/ a / 9386364 / 279590


3

또 다른 해, 또 다른 해결책! EF Core 용입니다.

DATETIME2(7)매핑 되는 많은 열이 DateTime있으며 항상 UTC를 저장합니다. 내 코드가 정확하면 오프셋이 항상 0이되기 때문에 오프셋을 저장하고 싶지 않습니다.

한편, 알 수없는 오프셋 (사용자가 제공)의 기본 날짜-시간 값을 저장하는 다른 열이 있으므로 "있는 그대로"저장 / 표시되고 다른 항목과 비교되지 않습니다.

따라서 특정 컬럼에 적용 할 수있는 솔루션이 필요합니다.

확장 방법을 정의합니다 UsesUtc.

private static DateTime FromCodeToData(DateTime fromCode, string name)
    => fromCode.Kind == DateTimeKind.Utc ? fromCode : throw new InvalidOperationException($"Column {name} only accepts UTC date-time values");

private static DateTime FromDataToCode(DateTime fromData) 
    => fromData.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(fromData, DateTimeKind.Utc) : fromData.ToUniversalTime();

public static PropertyBuilder<DateTime?> UsesUtc(this PropertyBuilder<DateTime?> property)
{
    var name = property.Metadata.Name;
    return property.HasConversion<DateTime?>(
        fromCode => fromCode != null ? FromCodeToData(fromCode.Value, name) : default,
        fromData => fromData != null ? FromDataToCode(fromData.Value) : default
    );
}

public static PropertyBuilder<DateTime> UsesUtc(this PropertyBuilder<DateTime> property)
{
    var name = property.Metadata.Name;
    return property.HasConversion(fromCode => FromCodeToData(fromCode, name), fromData => FromDataToCode(fromData));
}

그런 다음 모델 설정의 속성에 사용할 수 있습니다.

modelBuilder.Entity<CustomerProcessingJob>().Property(x => x.Started).UsesUtc();

올바른 유형의 속성에만 적용 할 수 있다는 점에서 속성에 비해 약간의 이점이 있습니다.

DB의 값이 UTC로되어 있지만 잘못된 것으로 가정합니다 Kind. 따라서 DB에 저장하려는 값을 관리하여 UTC가 아닌 경우 설명 예외를 발생시킵니다.


1
이것은 대부분의 새로운 개발이 Core 또는 .NET 5를 사용하기 때문에 특히 더 높아야하는 훌륭한 솔루션입니다. UTC 시행 정책에 대한 추가 가상 포인트-더 많은 사람들이 실제 사용자 표시까지 날짜를 UTC까지 유지한다면, 날짜 / 시간 버그는 거의 없습니다.
oflahero

1

리플렉션 구문 / 방법 제한이있는 .net 프레임 워크 4를 사용하여 @MattJohnson 솔루션을 달성해야하는 사람들을 위해 아래 나열된대로 약간의 수정이 필요합니다.

     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);
        }  

1

Matt Johnson-Pint의 솔루션은 작동하지만 모든 DateTime이 UTC 여야하는 경우 속성을 만드는 것이 너무 순환 적입니다. 단순화 한 방법은 다음과 같습니다.

public class MyContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }

    public MyContext()
    {
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
            (sender, e) => SetDateTimesToUtc(e.Entity);
    }

    private static void SetDateTimesToUtc(object entity)
    {
        if (entity == null)
        {
            return;
        }

        var properties = entity.GetType().GetProperties();
        foreach (var property in properties)
        {
            if (property.PropertyType == typeof(DateTime))
            {
                property.SetValue(entity, DateTime.SpecifyKind((DateTime)property.GetValue(entity), DateTimeKind.Utc));
            }
            else if (property.PropertyType == typeof(DateTime?))
            {
                var value = (DateTime?)property.GetValue(entity);
                if (value.HasValue)
                {
                    property.SetValue(entity, DateTime.SpecifyKind(value.Value, DateTimeKind.Utc));
                }
            }
        }
    }
}

0

또 다른 접근 방식은 datetime 속성을 사용하여 인터페이스를 만들고 부분 엔터티 클래스에서 구현하는 것입니다. 그런 다음 SavingChanges 이벤트를 사용하여 개체가 인터페이스 유형인지 확인하고 해당 datetime 값을 원하는대로 설정합니다. 실제로 이러한 날짜가 생성 / 수정 된 경우 해당 이벤트를 사용하여 채울 수 있습니다.


0

제 경우에는 UTC datetimes가있는 테이블이 하나만있었습니다. 내가 한 일은 다음과 같습니다.

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;
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.