是否可以让实体框架(我目前使用 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");
这是您可能会考虑的一种方法:
首先,定义以下属性:
[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
转换为其他时区。
对于 EF Core,GitHub 上有关于此主题的精彩讨论:https://github.com/dotnet/efcore/issues/4711
一种解决方案(归功于 Christopher Haws)将导致在将所有日期存储到数据库/从数据库中检索它们时将它们视为 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,请使用 check this link。
IsQueryType
(或现在的 IsKeyLess
)检查?
我真的很喜欢 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));
}
}
}
}
DateTIme
属性,则会失败。建议编辑。另请参阅stackoverflow.com/a/3762475/2279059
此答案适用于实体框架 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);
}
}
}
Dispose
和 GetData
?
又一年,另一种解决方案!这适用于 EF Core。
我有很多映射到 DateTime
的 DATETIME2(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
错误。因此,它会管理您尝试存储在数据库中的值,如果它们不是 UTC,则会引发描述性异常。
我相信我找到了一个不需要任何自定义 UTC 检查或 DateTime 操作的解决方案。
基本上,您需要更改 EF 实体以使用 DateTimeOffset (NOT DateTime) 数据类型。这将在数据库中存储带有日期值的时区(在我的例子中是 SQL Server 2015)。
当 EF Core 从数据库请求数据时,它也会收到时区信息。当您将此数据传递给 Web 应用程序(在我的情况下为 Angular2)时,日期会自动转换为浏览器的本地时区,这正是我所期望的。
当它被传回我的服务器时,它会再次自动转换为 UTC,也符合预期。
我现在正在研究这个,这些答案中的大多数都不是很好。据我所知,没有办法告诉 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 之外的任何人那里获得该设置器。
DateTimeKind.Utc
。示例:from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp };
将所有种类设置为 DateTimeKind.Unspecified
。
无法在实体框架中指定 DataTimeKind。您可能决定在存储到 db 之前将日期时间值转换为 utc,并始终假定从 db 检索的数据为 UTC。但是在查询期间具体化的 DateTime 对象将始终是“未指定”。您还可以使用 DateTimeOffset 对象而不是 DateTime 进行评估。
如果您在设置值时小心地正确传递 UTC 日期,并且您所关心的只是确保在从数据库中检索实体时正确设置 DateTimeKind,请在此处查看我的答案:https://stackoverflow.com/a/9386364/279590
感谢@ajcvickers。从 EF Core 2.1 开始,这将是处理 DateTime.Kind 的一种方法:
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
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));
}
}
}
}
}
这里的解决方案很有用,但我预计很多人会遇到这样的问题,即他们希望所有日期时间都在本地时区可用,但他们希望将其翻译,以便将持久版本保存为 UTC。
实现这一点有 3 个挑战:
将数据读取为 UTC 并转换为本地调整查询参数,例如 SELECT * From PRODUCT where SALEDATE< @1 将 LocalTime 的数据存储为 UTC
1. 以UTC格式读取数据并转换为Local
在这种情况下,上述基于 Ivan Stoev DateTime.Kind set to unspecified, not UTC, upon loading from database 工作的解决方案将满足您的需求。
2.调整查询参数
与 Ivan 的拦截器解决方案类似,您可以利用 ReaderExecuting 拦截器。好处是这比 ReaderExecuted 更容易实现。
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. 将 LocalTime 的数据存储为 UTC
虽然有些查询拦截器看起来像是在这里提供帮助,但它们被多次调用并产生了意想不到的结果。我想出的最佳解决方案是覆盖 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)));
}
}
}
对于像我一样需要使用.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);
}
为了在 Entity Framework Core 中为实体属性定义 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; }
}
对于其最简单的应用程序,您需要在 OnModelCreating 的末尾添加对 ApplyDateTimeKindConverter() 的调用。默认情况下,所有日期时间字段都分配有单一种类的 Utc。异常可以通过 DateTimeKindAttribute 属性或 fluentapi 方法 HasDateTimeKind (DateTimeKind.)
另一种方法是创建一个具有日期时间属性的接口,在部分实体类上实现它们。然后使用 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;
}
}
}
扩展了适用于我的 EF Dotnet Core 的先前解决方案放置代码。
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);
}
}
}
}
}
'System.Array' does not contain a definition for 'Where'