Entity FrameworkのDateTimeおよびUTC


96

Entity Framework(現在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");

回答:


144

以下は、検討すべきアプローチの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


7
これが機能しない場合は、次の使用法のいずれかが不足している可能性があります。System.Collections.Generic;を使用します。System.ComponentModel.DataAnnotations.Schemaを使用します。System.Linqを使用します。System.Reflectionを使用します。
Saustrup、2014年

7
@Saustrup-SOのほとんどの例では、質問に直接関連しない限り、簡潔にするために使用を省略しています。しかし、ありがとう。
Matt Johnson-Pint 2014年

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と互換性を持つようにいくつかの拡張機能を作成しました。もう1つの注意点は、これはプロジェクションでは機能せず、完全に読み込まれたエンティティのみで機能することです。
Mun

5
これを予測でうまくいくための提案はありますか?
Jafin 2015

32

私はマットジョンソンのアプローチが本当に好きですが、私のモデルではすべてのDateTimeメンバーがUTCであり、すべてのメンバーを属性で装飾する必要はありません。そのため、メンバーが明示的に属性で装飾されていない限り、イベントハンドラーがデフォルトのKind値を適用できるようにする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 2017

1
@Rhonage申し訳ありません。デフォルトはApplicationDbContextコンストラクタで設定されます。それを含めるように回答を更新しました。
Bob.at.Indigo.Health 2017

1
@ Bob.at.AIPsychLabありがとうメイト。何らかのリフレクションが起こっているかどうかを把握しようとしていましたが、いや、まったくシンプルです!
Rhonage 2017

これは、モデルにDateTIme(パブリック)セッターメソッドのない属性がある場合は失敗します。編集を提案しました。参照してくださいstackoverflow.com/a/3762475/2279059
フロリアン・冬の

13

この回答は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);
        }
    }
}

これまでのところ、これが最良の答えのようです。最初は属性のバリエーションを試してみましたが、到達距離が短​​いように見えましたが、OnModelCreatingイベントで発生するテーブルマッピングについてコンストラクターイベントの関連付けが認識されていないため、ユニットテストがモックで失敗します。これは私の投票になります!
の上院議員

1
なぜあなたはシャドウイングDisposeをしているのGetDataですか?
user247702

2
このコードは、おそらく@IvanStoevを信用しているはずです:stackoverflow.com/a/40349051/90287
Rami A.

残念ながら、空間データをマッピングしている場合、これは失敗します
Chris

@ user247702いやシャドウイング廃棄間違い、オーバーライドのDispose(ブール値)である
user2397863

9

カスタムUTCチェックやDateTime操作を必要としないソリューションを見つけたと思います。

基本的に、DateTimeOffset(DateTimeではない)データ型を使用するようにEFエンティティを変更する必要があります。これにより、タイムゾーンと日付の値がデータベースに格納されます(私の場合はSQL Server 2015)。

EF CoreがDBからデータを要求すると、タイムゾーン情報も受信します。このデータをWebアプリケーション(私の場合はAngular2)に渡すと、日付はブラウザーのローカルタイムゾーンに自動的に変換されます。

そして、それが私のサーバーに戻されると、期待どおりに、自動的に再びUTCに変換されます。


7
DateTimeOffsetは、一般的な認識とは異なり、タイムゾーンを格納しません。値が表すUTCからのオフセットを格納します。オフセットを逆にマッピングして、オフセットが作成された実際のタイムゾーンを特定することはできないため、データ型はほとんど役に立たなくなります。
Suncat2000 2017年

2
いいえ。ただし、DateTimeを正しく格納するために使用できます。medium.com
Carl

1
場所はどこでも同じなので、場所を必要としないのはUTCだけです。UTC以外のものを使用する場合は、場所も必要です。それ以外の場合、datetimeoffsetを使用しても、時間の情報は役に立ちません。
堀津

@ Suncat2000これは、特定の時点を保存するための最も賢明な方法です。他のすべての日付/時刻タイプもタイムゾーンを提供しません。
John

1
DATETIMEOFFSETは、元の投稿者が望んでいたことを実行します。(明示的な)変換を実行せずに、日時をUTCとして保存します。@Carl DATETIME、DATETIME2、およびDATETIMEOFFSETはすべて日時値を正しく格納します。UTCからのオフセットを追加で保存することを除いて、DATETIMEOFFSETにはほとんど利点がありません。データベースで使用するのは呼び出しです。多くの人が誤って考えているようなタイムゾーンが格納されないという点を家に帰りたかっただけです。
Suncat2000

5

Entity FrameworkでDataTimeKindを指定する方法はありません。日時の値をutcに変換してからdbに保存し、dbから取得したデータを常にUTCと見なすことを決定できます。ただし、クエリ中にマテリアライズされるDateTimeオブジェクトは常に「未指定」になります。また、DateTimeの代わりにDateTimeOffsetオブジェクトを使用して評価することもできます。


5

私は今これを研究しています、そしてこれらの答えのほとんどは正確に素晴らしいものではありません。私が見ることができることから、データベースから出てくる日付がUTC形式であることをEF6に伝える方法はありません。その場合、モデルのDateTimeプロパティがUTCであることを確認する最も簡単な方法は、セッターで検証して変換することです。

アルゴリズムを説明する擬似コードのような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);        
    }    
}

最初の2つのブランチは明らかです。ラストは秘伝のタレ。

EF6がデータベースからロードされたデータからモデルを作成するとき、DateTimeはDateTimeKind.Unspecifiedです。日付がすべてデータベースのUTCであることがわかっている場合は、最後のブランチが適切に機能します。

DateTime.Nowは常になDateTimeKind.Localので、上記のアルゴリズムはコードで生成された日付に対して正常に機能します。ほとんどの時間。

ただし、DateTimeKind.Unspecifiedコードをこっそり侵入する方法は他にもあるので、注意する必要があります。たとえば、JSONデータからモデルをデシリアライズすると、デシリアライザフレーバーはデフォルトでこの種類になります。DateTimeKind.UnspecifiedEF以外からセッターに到達することからマークされたローカライズされた日付を防ぐのはあなた次第です。


6
この問題に数年取り組んできた後でわかったように、DateTimeフィールドをデータ転送オブジェクトなどの他の構造に割り当てまたは選択している場合、EFはゲッターメソッドとセッターメソッドの両方を無視します。これらの場合でもDateTimeKind.Utc、結果が生成された後、Kindをに変更する必要があります。例: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 Coreについては、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として扱わないように除外する場合は、このリンクを確認してください


私にとって間違いなく最高の解決策です!おかげで
ベン・モリス

これはDateTimeOffsetで動作しますか?
マークレッドマン

1
@MarkRedman DateTimeOffsetの正当なユースケースがある場合は、タイムゾーンに関する情報も保持する必要があるため、これは意味がないと思います。DateTimeとDateTimeOffsetのどちらを選択するかについては、docs.microsoft.com / en - us / dotnet / standard / datetime / またはstackoverflow.com/a/14268167/3979621を参照してください。
Honza Kalfus

IsQueryType置き換えられているようだIsKeyLessgithub.com/dotnet/efcore/commit/...
マーク・Tielemans

4

値を設定するときにUTC日付を適切に渡すように注意し、エンティティがデータベースから取得されるときにDateTimeKindが適切に設定されていることを確認するだけの場合は、ここで私の答えを参照してください:https : //stackoverflow.com/ a / 9386364/279590


3

別の年、別のソリューション!これはEF Core用です。

DATETIME2(7)マップする列がたくさんありDateTime、常にUTCを格納します。私のコードが正しければオフセットは常にゼロになるので、オフセットを保存したくありません。

一方、(ユーザーから提供された)不明なオフセットの基本的な日時値を格納する他の列があるため、それらは単に「そのまま」格納/表示され、何と比較されません。

したがって、特定の列に適用できるソリューションが必要です。

拡張メソッドを定義し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イベントを使用して、オブジェクトがインターフェイス型かどうかを確認し、それらの日時の値を任意に設定します。実際、これらが日付の種類に基づいて作成/変更された場合、そのイベントを使用してデータを設定できます。


0

私の場合、UTC日時を含むテーブルは1つしかありませんでした。これが私がしたことです:

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.