Entity Framework always save DateTimeOffset as UTC

巧了我就是萌 提交于 2021-02-08 02:21:12

问题


Is there a way that I can instruct Entity Framework to always store DateTimeOffset as the UTC value. Technically there is no reason for this but I prefer it if all the data in my database is consistent.

Currently it is storing the DateTimeOffset as received from the client which could be anything depending on the users locale.

I was thinking of perhaps intercepting all the SaveChanges method, looping through the changes, looking for DateTimeOffset types and doing the conversion explicitly, but this seems like a lot of work which could be mitigated if EntityFramework has something built in.

Any insight or suggestions would be greatly appreciated.

EDIT: The difference between my post and others dealing with this topic is that my C# property is of type DateTimeOffset and my database field is also DateTimeOffset(0). There is not technical reason to convert other than consistency. If I store a value as "17:00 +5:00" or "10:00 -2:00" it doesn't matter, those are both identical moments in time. However, I would like to have all my dates stored as "12:00 +0:00" so that it is easier to read in SSMS or other DB tools.

EDIT 2: Seeing as there doesn't seem to be "simple" way to do this I am looking to now tackle the problem in the DbContext.SaveChanges() function. The problem is that no matter what I do, the value won't change? Here's the code so far:

public new void SaveChanges()
{
    foreach (var entry in this.ChangeTracker.Entries())
    {
        foreach (var propertyInfo in entry.Entity.GetType().GetProperties().Where(p => p.CanWrite && p.PropertyType == typeof(DateTimeOffset)))
        {
            var value = entry.CurrentValues.GetValue<DateTimeOffset>(propertyInfo.Name);

            var universalTime = value.ToUniversalTime();
            entry.CurrentValues[propertyInfo.Name] = universalTime;
            // entry.Property(propertyInfo.Name).CurrentValue = universalTime;

            // Adding a watch to "entry.CurrentValues[propertyInfo.Name]" reveals that it has not changed??
        }

        // TODO: DateTimeOffset?
    }

    base.SaveChanges();
}

EDIT 3 I finally managed to solve this, see answer below


回答1:


Something like this?

public System.DateTime TimeUTC
{
    get;
    set;
}

[NotMapped]
public System.DateTime LocalTime
{
    get
    {
        return this.TimeUTC.ToLocalTime();
    }

    set
    {
        this.TimeUTC = value.ToUniversalTime();
    }
}



回答2:


Thanks to the help I have received. I finally managed to get to the answer thanks to Jcl

public new void SaveChanges()
{
    foreach (var entry in this.ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Modified))
        {
        foreach (var propertyInfo in entry.Entity.GetType().GetTypeProperties().Where(p => p.PropertyType == typeof(DateTimeOffset)))
        {
            propertyInfo.SetValue(entry.Entity, entry.CurrentValues.GetValue<DateTimeOffset>(propertyInfo.Name).ToUniversalTime());
        }
        foreach (var propertyInfo in entry.Entity.GetType().GetTypeProperties().Where(p => p.PropertyType == typeof(DateTimeOffset?)))
        {
            var dateTimeOffset = entry.CurrentValues.GetValue<DateTimeOffset?>(propertyInfo.Name);
            if (dateTimeOffset != null) propertyInfo.SetValue(entry.Entity, dateTimeOffset.Value.ToUniversalTime());
        }
    }

     base.SaveChanges();
}

EDIT:

At the advice of Jcl, I wrote a small helper to cache the reflection:

public static class TypeReflectionExtension
{
    public static Dictionary<Type, PropertyInfo[]> PropertyInfoCache;

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

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

EDIT 2: To clean up the code a bit I created a generic function which you can use to intercept and modify any Data Type:

public static class DbEntityEntryExtension
{
    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)));
        }
    }
}

The usage of this is (Inside your DbContext):

public new void SaveChanges()
{
    foreach (var entry in this.ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Modified))
    {
        entry.ModifyTypes<DateTimeOffset>(ConvertToUtc);
        entry.ModifyTypes<DateTimeOffset?>(ConvertToUtc);
    }

    base.SaveChanges();
}

private static DateTimeOffset ConvertToUtc(DateTimeOffset dateTimeOffset)
{
    return dateTimeOffset.ToUniversalTime();
}

private static DateTimeOffset? ConvertToUtc(DateTimeOffset? dateTimeOffset)
{
    return dateTimeOffset?.ToUniversalTime();
}

Disclaimer This does solve my problem but is not recommended. I don't even know if I am going to use this as something about it just feels wrong now (Thanks Jcl ;). So please do use this with caution. It may probably be better that you follow the answer by Adam Benson



来源:https://stackoverflow.com/questions/39960923/entity-framework-always-save-datetimeoffset-as-utc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!