实体框架日期时间和 UTC

是否可以让实体框架(目前我使用 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");
107889 次浏览

无法在实体框架中指定 DataTimekind。在存储到 db 之前,您可以决定将日期时间值转换为 UTC,并始终假定从 db 检索到的数据为 UTC。但是在查询期间具体化的 DateTime 对象将始终是“未指定”的。还可以使用 DateTimeOffset 对象而不是 DateTime 进行计算。

另一种方法是创建一个带有 datetime 属性的接口,在部分实体类上实现它们。然后使用 SavingChanges 事件检查对象是否属于接口类型,将这些日期时间值设置为您想要的任何值。事实上,如果这些日期是创建/修改类型的日期,您可以使用该事件来填充它们。

在我的例子中,我只有一个包含 UTC 日期时间的表:

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

如果在设置值时小心地正确传递 UTC 日期,并且在从数据库检索实体时确保正确设置 DateTimekind,请参阅我的答案: https://stackoverflow.com/a/9386364/279590

这里有一个你可以考虑的方法:

首先,定义以下属性:

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

现在,对于任何 DateTimeDateTime?属性,都可以应用以下属性:

public class Foo
{
public int Id { get; set; }


[DateTimeKind(DateTimeKind.Utc)]
public DateTime Bar { get; set; }
}

这样,每当实体框架从数据库加载一个实体时,它将设置您指定的 DateTimeKind,例如 UTC。

请注意,这在保存时不执行任何操作。在尝试保存该值之前,仍然必须将该值正确地转换为 UTC。但是它确实允许您在检索时设置类型,这允许将其序列化为 UTC,或者使用 TimeZoneInfo将其转换为其他时区。

对于那些需要实现@MattJohnson 解决方案的人来说。像我一样的 net 框架4,由于反射语法/方法的限制,它需要稍微修改一下,如下所示:

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

我正在研究这个问题,大部分答案都不是很好。在我看来,没有办法告诉 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。如果您知道您的日期都是数据库中的 UTC,那么最后一个分支将非常适合您。

DateTime.Now始终是 DateTimeKind.Local,因此上面的算法可以很好地处理代码生成的日期。

但是,您必须小心谨慎,因为 DateTimeKind.Unspecified可以通过其他方式潜入您的代码。例如,您可以从 JSON 数据反序列化您的模型,并且反序列化器的风格默认为这种类型。这取决于你如何防止标记为 DateTimeKind.Unspecified的本地日期从除 EF 以外的任何人那里得到那个二传手。

我真的很喜欢 Matt Johnson 的方法,但是在我的模型中,所有的 DateTime 成员都是 UTC,我不想用一个属性来装饰它们。因此,我推广了 Matt 的方法,以允许事件处理程序应用默认 Kind 值,除非成员显式地用属性装饰。

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

我相信我已经找到了一个不需要任何自定义 UTC 检查或 DateTime 操作的解决方案。

基本上,您需要更改 EF 实体以使用 DateTimeOffset (NOTDateTime)数据类型。这将在数据库中存储带有日期值的时区(在我的示例中为 SQLServer2015)。

当 EF 核心要求从数据库的数据将收到时区信息以及。 当你将这些数据传递给一个 web 应用程序(我的情况是 Angular2)时,日期会自动转换为浏览器的本地时区,这正是我所期望的。

当它被传递回我的服务器时,它会再次自动转换为 UTC,这也是预期的。

这个答案适用于 实体框架6

接受的答案不适用于 Projected 或 Anonymous 对象。性能也可能是一个问题。

为此,我们需要使用由 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);
}
}
}

Matt Johnson-Pint 的解决方案是可行的,但是如果所有的 DateTimes 都应该是 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));
}
}
}
}
}

新的一年,新的解决方案! 这是英孚核心。

我有很多映射到 DateTimeDATETIME2(7)列,并且总是存储 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();

相对于只能将其应用于正确类型的属性的属性,它有一个小优点。

请注意,它假设数据库中的值是 UTC,但只是有错误的 Kind。因此,它会监视您试图存储在 DB 中的值,如果它们不是 UTC,则抛出描述性异常。

对于 EF Core,在 GitHub 上有一个关于这个主题的很好的讨论: https://github.com/dotnet/efcore/issues/4711

将所有日期存储到/从数据库中检索它们时作为 UTC 处理的解决方案(归功于 克里斯托弗 · 霍斯)是在 DbContext类的 OnModelCreating方法中添加以下内容:

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.IsKeyless)
{
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。

这里的解决方案是有用的,但是我希望很多人会遇到这样的问题: 他们希望所有的日期时间在本地时区可用,但是他们希望将其转换为保存 UTC 的持久版本。

实现这一目标面临3项挑战:

  1. 以 UTC 格式读取数据并转换为 Local
  2. 调整查询参数,例如 SELECT * From PRODUCT where SALEDATE <@1
  3. 以 UTC 格式存储 LocalTime 数据

1. 以 UTC 格式读取数据并转换为 Local

在这种情况下,上述基于 Ivan Stoev 从数据库加载时,类别设置为未指定,而不是 UTC工作的解决方案将满足您的需要。

2. 调整查询参数

与 Ivan 的拦截器解决方案类似,您可以使用 ReaderExecting 拦截器。额外的好处是,这比 ReaderExected 更容易实现。

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
foreach (DbParameter dbParameter in command.Parameters)
{
if (dbParameter.Value is DateTime dtLocal)
{
if (dtLocal.Kind != DateTimeKind.Utc)
{
dbParameter.Value = dtLocal.ToUniversalTime();
}
}
}
base.ReaderExecuting(command, interceptionContext);
}

3. 存储以 UTC 格式显示的 LocalTime 数据

虽然有一些查询拦截器似乎可以在这里提供帮助,但是它们被多次调用并创建了意外的结果。我想到的最佳解决方案是重写 SaveChanges

    public override int SaveChanges()
{
UpdateCommonProperties();
UpdateDatesToUtc();
bool saveFailed;
do
{
saveFailed = false;
try
{
var result = base.SaveChanges();
return result;
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = ConcurrencyExceptionHandler(ex);
}


} while (saveFailed);
return 0;
}


private void UpdateDatesToUtc()
{
if (!ChangeTracker.HasChanges()) return;


var modifiedEntries = ChangeTracker.Entries().Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));


foreach (var entry in modifiedEntries)
{
entry.ModifyTypes<DateTime>(ConvertToUtc);
entry.ModifyTypes<DateTime?>(ConvertToUtc);
}
}


private static DateTime ConvertToUtc(DateTime dt)
{
if (dt.Kind == DateTimeKind.Utc) return dt;
return dt.ToUniversalTime();
}


private static DateTime? ConvertToUtc(DateTime? dt)
{
if (dt?.Kind == DateTimeKind.Utc) return dt;
return dt?.ToUniversalTime();
}

扩展是(基于 Talon https://stackoverflow.com/a/39974362/618660的响应

public static class TypeReflectionExtension
{
static Dictionary<Type, PropertyInfo[]> PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();


static void TypeReflectionHelper()
{
PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();
}


public static PropertyInfo[] GetTypeProperties(this Type type)
{
if (!PropertyInfoCache.ContainsKey(type))
{
PropertyInfoCache[type] = type.GetProperties();
}
return PropertyInfoCache[type];
}


public static void ModifyTypes<T>(this DbEntityEntry dbEntityEntry, Func<T, T> method)
{
foreach (var propertyInfo in dbEntityEntry.Entity.GetType().GetTypeProperties().Where(p => p.PropertyType == typeof(T) && p.CanWrite))
{
propertyInfo.SetValue(dbEntityEntry.Entity, method(dbEntityEntry.CurrentValues.GetValue<T>(propertyInfo.Name)));
}
}
}

扩展了以前的解决方案的 EF 网点核心把代码,为我工作。

 public partial class XxxxxxDataContext {
partial void CustomizeMapping(ref ModelBuilder modelBuilder) {
var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
v => v.ToUniversalTime(),
(v) => v.Kind == DateTimeKind.Utc?DateTime.SpecifyKind(v, DateTimeKind.Utc):TimeZoneInfo.ConvertTimeToUtc(v, TimeZoneInfo.Local));


var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
v => v.HasValue ? v.Value.ToUniversalTime() : v,
v => v.HasValue ? v.Value.Kind == DateTimeKind.Utc ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : TimeZoneInfo.ConvertTimeToUtc(v.Value, TimeZoneInfo.Local) : v);


foreach (var entityType in modelBuilder.Model.GetEntityTypes()) {
if (entityType.IsKeyless) {
continue;
}


foreach (var property in entityType.GetProperties()) {
if (property.ClrType == typeof(DateTime)) {
property.SetValueConverter(dateTimeConverter);
} else if (property.ClrType == typeof(DateTime?)) {
property.SetValueConverter(nullableDateTimeConverter);
}
}
}
}
}

要为实体框架核心中的实体属性定义 DateTimekind,我建议使用以下类:

/// <summary>
/// Класс для преобразования DateTimeKind у сущностей в EF
/// </summary>
public static class DateTimeKindAnnotation
{
private const string DateTimeKindAnnotation = "DateTimeKind";


private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));


private static readonly ValueConverter<DateTime, DateTime> LocalConverter =
new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Local));


private static readonly ValueConverter<DateTime, DateTime> UnspecifiedConverter =
new ValueConverter<DateTime, DateTime>(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Unspecified));


/// <summary>
/// Данное свойство будет иметь заданный DateTimeKind в EF
/// </summary>
/// <typeparam name="TProperty"></typeparam>
/// <param name="builder"></param>
/// <param name="kind"></param>
public static PropertyBuilder<DateTime> HasDateTimeKind(this PropertyBuilder<DateTime> builder, DateTimeKind kind) =>
builder.HasAnnotation(DateTimeKindAnnotation, kind);


/// <summary>
/// Данное свойство будет иметь заданный DateTimeKind в EF
/// </summary>
/// <typeparam name="TProperty"></typeparam>
/// <param name="builder"></param>
/// <param name="kind"></param>
public static PropertyBuilder<DateTime?> HasDateTimeKind(this PropertyBuilder<DateTime?> builder, DateTimeKind kind) =>
builder.HasAnnotation(DateTimeKindAnnotation, kind);


public static DateTimeKind? FindDateTimeKind(this IMutableProperty property)
{
var attribute = property.PropertyInfo.GetCustomAttribute<DateTimeKindAttribute>();
if (attribute is not null)
{
return attribute.Kind;
}
return (DateTimeKind?)property.FindAnnotation(DateTimeKindAnnotation)?.Value;
}


/// <summary>
/// Преобразует DateTimeKind у всех сущностей в EF к значению по умолчанию, заданному через атрибут или анотацию.
/// </summary>
/// <remarks>Убедитесь, что это вызывается после настройки всех ваших сущностей.</remarks>
/// <param name="builder"></param>
/// <param name="defaultKind">DateTimeKind, который надо использовать по умолчанию.</param>
public static void ApplyDateTimeKindConverter(this ModelBuilder builder, DateTimeKind defaultKind = DateTimeKind.Utc)
{
foreach (var entityType in builder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
{
DateTimeKind kind = property.FindDateTimeKind() ?? defaultKind;
switch (kind)
{
case DateTimeKind.Utc:
property.SetValueConverter(UtcConverter);
break;
case DateTimeKind.Local:
property.SetValueConverter(LocalConverter);
break;
case DateTimeKind.Unspecified:
property.SetValueConverter(UnspecifiedConverter);
break;
default:
throw new NotSupportedException($"Kind \"{kind}\" неподдерживается");
}
}
}
}
}
}


/// <summary>
/// Задает тот DateTimeKind, который будет применяться EF для поля сущности.
/// </summary>
public class DateTimeKindAttribute : Attribute
{
public DateTimeKindAttribute(DateTimeKind kind) => Kind = kind;
public DateTimeKind Kind { get; }
}

对于其最简单的应用程序,您需要在 OnModelCreate 的末尾添加对 ApplicyDateTimeKindConverter ()的调用。默认情况下,所有 Datetime 字段都被分配一种 Utc。异常可以通过 DateTimeKindAttribute 属性或 fluentapi 方法 HasDateTimekind (DateTimekind.)实现

从 EF Core 2.1开始,这将是处理日期时间的一种方法。善良:

modelBuilder
.Entity<Foo>()
.Property(e => e.SomeDate)
.HasConversion(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

这将确保每次从数据库读取日期时,它都被自动指定为 Utc。

来源:

Https://github.com/dotnet/efcore/issues/4711#issuecomment-358695190

在 EF Core 6.0中,添加了 DbContext.ConfigureConvention () ,它可以为特定类型的所有属性注册转换。 (https://learn.microsoft.com/en-us/ef/core/modeling/bulk-configuration#pre-convention-configuration)

下面是使用 ConfigureConvention 的解决方案:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<DateTime>()
.HaveConversion(typeof(UtcValueConverter));
}


class UtcValueConverter : ValueConverter<DateTime, DateTime>
{
public UtcValueConverter()
: base(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc))
{
}
}

https://github.com/dotnet/efcore/issues/4711#issuecomment-1048572602提供