Fio*_*onn 87 c# datetime entity-framework utc code-first
是否有可能拥有实体框架(我目前使用的是Code First Approach with 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");
Run Code Online (Sandbox Code Playgroud)
Mat*_*int 136
您可以考虑以下一种方法:
首先,定义以下属性:
[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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在将该属性挂钩到您的EF上下文:
public class MyContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public MyContext()
{
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
(sender, e) => DateTimeKindAttribute.Apply(e.Entity);
}
}
Run Code Online (Sandbox Code Playgroud)
现在,在任何DateTime或DateTime?属性上,您都可以应用此属性:
public class Foo
{
public int Id { get; set; }
[DateTimeKind(DateTimeKind.Utc)]
public DateTime Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
有了这个,每当Entity Framework从数据库加载实体时,它将设置DateTimeKind您指定的实体,例如UTC.
请注意,保存时这不会执行任何操作.在尝试保存之前,您仍需要将值正确转换为UTC.但它确实允许您在检索时设置类型,允许将其序列化为UTC,或者将其转换为其他时区TimeZoneInfo.
Hon*_*fus 33
对于EF Core,在 GitHub 上有一个关于这个主题的很好的讨论:https : //github.com/dotnet/efcore/issues/4711
一种解决方案(归功于Christopher Haws)将导致在将所有日期存储到数据库/从数据库中检索它们时将它们作为 UTC 进行处理的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.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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
此外,如果您想排除某些实体的某些属性被视为 UTC ,请检查此链接。
Bob*_*lth 29
我非常喜欢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);
}
Run Code Online (Sandbox Code Playgroud)
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));
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Ham*_*edH 26
在 EF Core 6.0 中添加了 DbContext.ConfigureConventions(),它可以注册特定类型的所有属性的转换。(https://learn.microsoft.com/en-us/ef/core/modeling/bulk-configuration#pre-convention-configuration)
这是使用ConfigureConventions的解决方案:
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))
{
}
}
Run Code Online (Sandbox Code Playgroud)
由https://github.com/dotnet/efcore/issues/4711#issuecomment-1048572602提供
Dan*_*ker 10
Another year, another solution! This is for EF Core.
I have a lot of DATETIME2(7) columns that map to DateTime, and always store UTC. I don't want to store an offset because if my code is correct then the offset will always be zero.
Meanwhile I have other colums that store basic date-time values of unknown offset (provided by users), so they are just stored/displayed "as is", and not compared with anything.
Therefore I need a solution that I can apply to specific columns.
定义扩展方法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));
}
Run Code Online (Sandbox Code Playgroud)
然后可以将其用于模型设置中的属性:
modelBuilder.Entity<CustomerProcessingJob>().Property(x => x.Started).UsesUtc();
Run Code Online (Sandbox Code Playgroud)
与属性相比,它有一个较小的优势,即您只能将其应用于正确类型的属性。
请注意,它假定来自 DB 的值采用 UTC 格式,但只是有错误的Kind. 因此,它会管理您尝试存储在数据库中的值,如果它们不是 UTC,则会引发描述性异常。
小智 9
这个答案适用于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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
在你的注册中注册拦截器 DbConfiguration
internal class MyDbConfiguration : DbConfiguration
{
protected internal MyDbConfiguration ()
{
AddInterceptor(new UtcInterceptor());
}
}
Run Code Online (Sandbox Code Playgroud)
最后,注册您的配置 DbContext
[DbConfigurationType(typeof(MyDbConfiguration ))]
internal class MyDbContext : DbContext
{
// ...
}
Run Code Online (Sandbox Code Playgroud)
而已.干杯.
为简单起见,这里是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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我相信我找到了一个不需要任何自定义UTC检查或DateTime操作的解决方案.
基本上,您需要更改EF实体以使用DateTimeOffset(NOT DateTime)数据类型.这将在数据库中存储带有日期值的时区(在我的情况下为SQL Server 2015).
当EF Core从数据库请求数据时,它也会收到时区信息.当您将此数据传递给Web应用程序(在我的情况下为Angular2)时,日期会自动转换为浏览器的本地时区,这正是我所期望的.
当它被传递回我的服务器时,它会自动再次转换为UTC,同样如预期的那样.
感谢@ajcvickers。从 EF Core 2.1 开始,这将是处理 DateTime.Kind 的一种方法:
modelBuilder
.Entity<Foo>()
.Property(e => e.SomeDate)
.HasConversion(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
Run Code Online (Sandbox Code Playgroud)
这将确保每次从数据库读取日期时都会自动将其指定为 Utc。
来源:
https://github.com/dotnet/efcore/issues/4711#issuecomment-358695190
小智 6
我现在正在研究这个问题,而且大多数答案都不是很好.从我所看到的,没有办法告诉EF6数据库的日期是UTC格式.如果是这种情况,确保模型的DateTime属性是UTC的最简单方法是在setter中验证和转换.
这里有一些c#like pseudocode描述了算法
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);
}
}
Run Code Online (Sandbox Code Playgroud)
前两个分支是显而易见的.最后一个拿着秘密酱.
当EF6根据从数据库加载的数据创建模型时,DateTimes是DateTimeKind.Unspecified.如果您知道您的日期在数据库中都是UTC,那么最后一个分支将非常适合您.
DateTime.Now总是DateTimeKind.Local如此,因此上述算法适用于代码中生成的日期.大多数时候.
但是,您必须谨慎,因为还有其他方法DateTimeKind.Unspecified可以潜入您的代码.例如,您可以从JSON数据反序列化模型,并且您的反序列化程序风格默认为此类.这取决于你要防止标记为DateTimeKind.Unknown从除了EF之外的任何人获得该设置者的本地化日期.
无法在实体框架中指定 DataTimeKind。您可以决定在存储到 db 之前将日期时间值转换为 utc,并始终假定从 db 检索的数据为 UTC。但是在查询期间具体化的 DateTime 对象将始终为“未指定”。您还可以使用 DateTimeOffset 对象而不是 DateTime 进行评估。
| 归档时间: |
|
| 查看次数: |
51668 次 |
| 最近记录: |