Oto jedno podejście, które możesz rozważyć:
Najpierw zdefiniuj następujący atrybut:
[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));
}
}
}
Teraz podłącz ten atrybut do kontekstu EF:
public class MyContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public MyContext()
{
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
(sender, e) => DateTimeKindAttribute.Apply(e.Entity);
}
}
Teraz w dowolnych usługach DateTime
lub DateTime?
możesz zastosować ten atrybut:
public class Foo
{
public int Id { get; set; }
[DateTimeKind(DateTimeKind.Utc)]
public DateTime Bar { get; set; }
}
Dzięki temu za każdym razem, gdy Entity Framework ładuje jednostkę z bazy danych, ustawi to DateTimeKind
, co określisz, na przykład UTC.
Pamiętaj, że to nic nie robi podczas zapisywania. Nadal będziesz musiał poprawnie przekonwertować wartość na UTC, zanim spróbujesz ją zapisać. Ale pozwala ustawić rodzaj podczas pobierania, co pozwala na serializację jako UTC lub konwersję do innych stref czasowych za pomocą TimeZoneInfo
.
'System.Array' does not contain a definition for 'Where'
Bardzo podoba mi się podejście Matta Johnsona, ale w moim modelu WSZYSCY moi członkowie DateTime należą do UTC i nie chcę, aby każdy z nich był dekorowany atrybutem. Więc uogólniłem podejście Matta, aby umożliwić obsłudze zdarzeń zastosowanie domyślnej wartości Kind, chyba że element członkowski jest jawnie ozdobiony atrybutem.
Konstruktor klasy ApplicationDbContext zawiera następujący kod:
/// <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
wygląda tak:/// <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)); } } } }
źródło
DateTIme
atrybut bez (publicznej) metody ustawiającej. Zaproponowana edycja. Zobacz także stackoverflow.com/a/3762475/2279059Zaakceptowana odpowiedź nie działa dla obiektu projektowanego lub anonimowego. Wydajność też może być problemem.
Aby to osiągnąć, musimy użyć
DbCommandInterceptor
obiektu dostarczonego przez EntityFramework.Utwórz przechwytywacz:
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
to DbDataReader, który zastępujemy naszympublic 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); } }
Zarejestruj przechwytywacz w swoim
DbConfiguration
internal class MyDbConfiguration : DbConfiguration { protected internal MyDbConfiguration () { AddInterceptor(new UtcInterceptor()); } }
Na koniec zarejestruj konfigurację dla swojego
DbContext
[DbConfigurationType(typeof(MyDbConfiguration ))] internal class MyDbContext : DbContext { // ... }
Otóż to. Twoje zdrowie.
Dla uproszczenia oto cała implementacja 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); } } }
źródło
Dispose
iGetData
?W przypadku EF Core jest świetna dyskusja na ten temat w serwisie GitHub: https://github.com/dotnet/efcore/issues/4711
Rozwiązaniem (uznanie dla Christophera Hawsa ), które spowoduje traktowanie wszystkich dat podczas przechowywania ich do / pobierania z bazy danych jako UTC, jest dodanie następujących elementów do
OnModelCreating
metody TwojejDbContext
klasy: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); } } }
Ponadto, sprawdź ten link , jeśli chcesz wykluczyć pewne właściwości niektórych podmiotów z leczony jako UTC.
źródło
IsQueryType
wydaje się, że został zastąpiony przezIsKeyLess
: github.com/dotnet/efcore/commit/…IsQueryType
(lubIsKeyLess
teraz) czek?Uważam, że znalazłem rozwiązanie, które nie wymaga żadnego niestandardowego sprawdzania UTC ani manipulacji datą i godziną.
Zasadniczo musisz zmienić jednostki EF, aby używały typu danych DateTimeOffset (NOT DateTime). Spowoduje to zapisanie strefy czasowej z wartością daty w bazie danych (w moim przypadku SQL Server 2015).
Gdy EF Core zażąda danych z bazy danych, otrzyma również informacje o strefie czasowej. Kiedy przekazujesz te dane do aplikacji internetowej (w moim przypadku Angular2), data jest automatycznie konwertowana na lokalną strefę czasową przeglądarki, czego oczekuję.
A kiedy jest przesyłany z powrotem na mój serwer, jest ponownie automatycznie konwertowany na UTC, również zgodnie z oczekiwaniami.
źródło
Badam to teraz i większość z tych odpowiedzi nie jest do końca świetna. Z tego, co widzę, nie sposób powiedzieć EF6, że daty wychodzące z bazy danych są w formacie UTC. W takim przypadku najprostszym sposobem upewnienia się, że właściwości DateTime modelu są w formacie UTC, byłoby zweryfikowanie i przekonwertowanie metody ustawiającej.
Oto pseudokod podobny do języka C #, który opisuje algorytm
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); } }
Pierwsze dwie gałęzie są oczywiste. Ostatni zawiera sekretny sos.
Gdy EF6 tworzy model na podstawie danych załadowanych z bazy danych, DateTimes są
DateTimeKind.Unspecified
. Jeśli wiesz, że wszystkie twoje daty są w bazie danych UTC, ostatnia gałąź będzie dla ciebie świetna.DateTime.Now
jest zawszeDateTimeKind.Local
, więc powyższy algorytm działa dobrze dla dat wygenerowanych w kodzie. Większość czasu.Musisz jednak być ostrożny, ponieważ istnieją inne sposoby,
DateTimeKind.Unspecified
aby wkraść się do Twojego kodu. Na przykład możesz deserializować modele z danych JSON, a smak deserializatora będzie domyślnie ustawiony na ten rodzaj. To do Ciebie należy ochrona przed zlokalizowanymi datami oznaczonymiDateTimeKind.Unspecified
przed dotarciem do tego setera od kogokolwiek poza EF.źródło
DateTimeKind.Utc
po wygenerowaniu wyników. Przykład:from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp };
ustawia wszystkie rodzaje naDateTimeKind.Unspecified
.Nie ma sposobu, aby określić DataTimeKind w Entity Framework. Możesz zdecydować się na konwersję wartości daty i czasu na utc przed zapisaniem ich do db i zawsze zakładać, że dane pobrane z db są podane jako UTC. Ale obiekty DateTime zmaterializowane podczas zapytania zawsze będą miały wartość „Nieokreślone”. Możesz również evalualte używając obiektu DateTimeOffset zamiast DateTime.
źródło
Kolejny rok, kolejne rozwiązanie! To jest dla EF Core.
Mam wiele
DATETIME2(7)
kolumn, które odwzorowująDateTime
i zawsze przechowują czas UTC. Nie chcę przechowywać przesunięcia, ponieważ jeśli mój kod jest poprawny, przesunięcie będzie zawsze wynosić zero.W międzyczasie mam inne kolumny, które przechowują podstawowe wartości daty i czasu o nieznanym przesunięciu (podane przez użytkowników), więc są one po prostu przechowywane / wyświetlane „tak jak jest” i nie są porównywane z niczym.
Dlatego potrzebuję rozwiązania, które mogę zastosować do konkretnych kolumn.
Zdefiniuj metodę rozszerzenia
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)); }
Można to następnie użyć we właściwościach w konfiguracji modelu:
Ma niewielką przewagę nad atrybutami, ponieważ można ją zastosować tylko do właściwości odpowiedniego typu.
Zauważ, że zakłada, że wartości z DB są w UTC, ale po prostu mają błędne
Kind
. W związku z tym kontroluje wartości, które próbujesz przechowywać w bazie danych, zgłaszając opisowy wyjątek, jeśli nie są to UTC.źródło
Jeśli uważasz, aby poprawnie przekazywać daty UTC podczas ustawiania wartości i wszystko, na czym Ci zależy, to upewnienie się, że DateTimeKind jest poprawnie ustawiony, gdy jednostki są pobierane z bazy danych, zobacz moją odpowiedź tutaj: https://stackoverflow.com/ a / 9386364/279590
źródło
Dla tych, którzy chcą osiągnąć rozwiązanie @MattJohnson z .NET Framework 4, takim jak ja, z odbiciem składni / ograniczeniem metody, wymaga to niewielkiej modyfikacji, jak podano poniżej:
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); }
źródło
Rozwiązanie Matta Johnsona-Pinta działa, ale jeśli wszystkie Twoje daty mają być UTC, tworzenie atrybutu byłoby zbyt okrężne. Oto jak to uprościłem:
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)); } } } } }
źródło
Innym podejściem byłoby utworzenie interfejsu z właściwościami datetime i zaimplementowanie ich w częściowych klasach jednostek. Następnie użyj zdarzenia SavingChanges, aby sprawdzić, czy obiekt jest typu interfejsu, ustaw te wartości daty i godziny na dowolne. W rzeczywistości, jeśli są one tworzone / modyfikowane w określonych datach, możesz użyć tego zdarzenia, aby je wypełnić.
źródło
W moim przypadku miałem tylko jedną tabelę z czasami UTC. Oto co zrobiłem:
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; } } }
źródło
Rozwiązania tutaj są przydatne, ale spodziewam się, że wielu podejdzie do tego z problemem, że chcą, aby wszystkie ich czasy danych były dostępne w lokalnej strefie czasowej, ale chcą, aby były przetłumaczone, aby utrwalona wersja została zapisana w UTC.
Istnieją 3 wyzwania, aby to zrealizować:
1. Odczyt danych w formacie UTC i konwersja do formatu lokalnego
W takim przypadku powyższe rozwiązanie oparte na pracy Ivana Stoeva DateTime.Kind ustawiony na nieokreślony, a nie UTC, po załadowaniu z bazy danych zrobi to, czego potrzebujesz.
2. Dostosowanie parametrów zapytania
Podobnie do rozwiązania Ivana dla przechwytywacza, możesz użyć przechwytywacza ReaderExecuting. Dodatkową zaletą jest to, że jest to znacznie łatwiejsze do wdrożenia niż 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. Przechowywanie danych w czasie lokalnym w formacie UTC
Chociaż istnieją przechwytywacze zapytań, które wyglądają tak, jakby pomagały w tym miejscu, są wywoływane wiele razy i generują nieoczekiwane wyniki. Najlepszym rozwiązaniem, jakie wymyśliłem, było zastąpienie 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(); }
A rozszerzenie to (na podstawie odpowiedzi Talona 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))); } } }
źródło