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");
考えられるアプローチの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はデータベースからエンティティをロードするたびに、UTCなど、指定したDateTimeKind
を設定します。
これは保存時に何もしないことに注意してください。保存する前に、値を正しくUTCに変換する必要があります。ただし、取得時に種類を設定できるため、UTCとしてシリアル化したり、TimeZoneInfo
を使用して他のタイムゾーンに変換したりできます。
私はマット・ジョンソンのアプローチが本当に好きですが、私のモデルでは、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));
}
}
}
}
この回答はEntity Framework 6で機能します
受け入れられた回答は、投影オブジェクトまたは匿名オブジェクトでは機能しません。パフォーマンスも問題になる可能性があります。
これを実現するには、EntityFrameworkが提供するオブジェクトであるDbCommandInterceptor
を使用する必要があります。
インターセプターの作成:
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にデータを要求すると、タイムゾーン情報も受信します。このデータをWebアプリケーション(私の場合はAngular2)に渡すと、日付はブラウザーのローカルタイムゾーンに自動的に変換されます。
そして、それが私のサーバーに戻されるとき、それもまた予想通り、自動的に再びUTCに変換されます。
私は今これを調査していますが、これらの答えのほとんどは正確ではありません。私が見ることができることから、データベースから出てくる日付が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がデータベースからロードされたデータからモデルを作成する場合、DateTimesはDateTimeKind.Unspecified
です。データベースの日付がすべてUTCであることがわかっている場合は、最後のブランチが最適です。
DateTime.Now
は常にDateTimeKind.Local
であるため、上記のアルゴリズムはコードで生成された日付に対して正常に機能します。ほとんどの時間。
ただし、DateTimeKind.Unspecified
がコードに侵入する可能性がある他の方法があるため、注意する必要があります。たとえば、JSONデータからモデルをデシリアライズすると、デシリアライザーフレーバーはこの種類にデフォルト設定されます。 DateTimeKind.Unknown
とマークされたローカライズされた日付がEF以外からセッターに到達するのを防ぐのはあなた次第です。
値を設定するときにUTC日付を適切に渡すように注意し、エンティティがデータベースから取得されるときにDateTimeKindが適切に設定されていることを確認することだけが必要な場合は、こちらの回答を参照してください: https:// stackoverflow.com/a/9386364/27959
Entity FrameworkでDataTimeKindを指定する方法はありません。 dbに保存する前に、日付時刻値をutcに変換し、dbから取得したデータを常にUTCと見なすことができます。ただし、クエリ中に標準化されたDateTimeオブジェクトは常に「指定なし」になります。 DateTimeの代わりにDateTimeOffsetオブジェクトを使用して評価することもできます。
私のような.net framework 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);
}
私の場合、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;
}
}
}
別のアプローチは、datetimeプロパティを持つインターフェイスを作成し、それらを部分エンティティクラスに実装することです。次に、SavingChangesイベントを使用して、オブジェクトがインターフェイスタイプであるかどうかを確認し、それらの日時値を必要な値に設定します。実際、これらの日付が一種の日付に作成/変更された場合、そのイベントを使用してデータを設定できます。