dotnetcore / osharp

OSharp是一个基于.Net6.0的快速开发框架,框架对 AspNetCore 的配置、依赖注入、日志、缓存、实体框架、Mvc(WebApi)、身份认证、功能权限、数据权限等模块进行更高一级的自动化封装,并规范了一套业务实现的代码结构与操作流程,使 .Net 框架更易于应用到实际项目开发中。
Apache License 2.0
2.79k stars 752 forks source link

数据库表里所有时间字段,修改为utc时间 #159

Closed wdydxf closed 4 years ago

wdydxf commented 4 years ago

您的功能请求与现有问题有关吗?请描述

数据库表的时间字段, 能否修改为UTC时间, 框架如果能统一处理的话就更好了

描述您想要的解决方案

  1. 数据库表的时间字段全部存储为utc时间.
  2. 返回给前端时, 自动转为当前登录用户的时区

描述你考虑过的替代方案

现在方案也能用, 但如果框架能覆盖该功能的话, 适用更加广泛

附加上下文,比如截图

gmf520 commented 4 years ago

是否使用UTC时间还是应该按项目实际需要进行选择吧,我觉得框架武断引入会增加不少复杂度

gmf520 commented 4 years ago

给实体的DateTime类型属性添加UTC存储支持

需求

  1. 数据库中的时间存储为 DateTimeKind.Utc 类型
  2. C#代码中的时间存取使用 DateTimeKind.Local 类型
  3. 由 EFCore 负责将C#代码中的Local时间转换为Utc时间存储到数据库,将从数据库取出的Utc时间转换为Local时间到C#代码中使用

    EFCore中技术支持

    在实体的EntityConfiguration中,对时间类型的属性添加转换器,如下:

    builder.Property(m => m.CreatedTime).HasConversion(local => local.ToUniversalTime(), utc => utc.ToLocalTime());

    如此,每个实体的配置都要添加转换器,是件繁琐的事,可以通过DI注入解决

    DI服务实现方式

    1. 添加服务接口

    namespace OSharp.Entity
    {
    /// <summary>
    /// 实体时间属性UTC转换器
    /// </summary>
    public interface IEntityDateTimeUtcConversion
    {
        /// <summary>
        /// 转换指定的实体类型。
        /// </summary>
        /// <param name="entityType">实体类型</param>
        void Convert(IMutableEntityType entityType);
    }
    }

    2. 服务实现

    namespace OSharp.Entity
    {
    /// <summary>
    /// 实体时间属性UTC转换器
    /// </summary>
    /// <seealso cref="OSharp.Entity.IEntityDateTimeUtcConversion" />
    public class EntityDateTimeUtcConversion : IEntityDateTimeUtcConversion
    {
        private readonly ValueConverter<DateTime, DateTime> _dateTimeConverter;
        private readonly ValueConverter<DateTime?, DateTime?> _nullableDateTimeConverter;
    
        /// <summary>
        /// 初始化一个<see cref="EntityDateTimeUtcConversion"/>类型的新实例
        /// </summary>
        public EntityDateTimeUtcConversion()
        {
            _dateTimeConverter = new ValueConverter<DateTime, DateTime>(
                local => local.ToUniversalTime(),
                utc => utc.ToLocalTime());
            _nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
                local => local.HasValue ? local.Value.ToUniversalTime() : local,
                utc => utc.HasValue ? utc.Value.ToLocalTime() : utc);
        }
    
        /// <summary>
        /// 转换指定的实体类型。
        /// </summary>
        /// <param name="entityType">实体类型</param>
        public void Convert(IMutableEntityType entityType)
        {
            foreach (IMutableProperty property in entityType.GetProperties())
            {
                if (property.ClrType == typeof(DateTime))
                {
                    property.SetValueConverter(_dateTimeConverter);
                }
                else if (property.ClrType == typeof(DateTime?))
                {
                    property.SetValueConverter(_nullableDateTimeConverter);
                }
            }
        }
    }
    }

    3. 添加服务

    EntityFrameworkCorePackBase

    services.TryAddSingleton<IEntityDateTimeUtcConversion, EntityDateTimeUtcConversion>();

    4. 添加数据上下文配置

    添加配置选项 DateTimeUtcFormatEnabled,作为上下文是否启用UTC时间存储的开关

    "SqlServer": {
    "DbContextTypeName": "OSharp.Entity.DefaultDbContext,OSharp.EntityFrameworkCore",
    "ConnectionString": "Data Source=osharp-mvc-dev.db",
    "DatabaseType": "Sqlite",
    "LazyLoadingProxiesEnabled": true,
    "DateTimeUtcFormatEnabled": true,
    "AuditEntityEnabled": true,
    "AutoMigrationEnabled": true
    }

    5. 在 DbContextBase 的 OnModelCreating 应用服务

    List<IMutableEntityType> entityTypes = modelBuilder.Model.GetEntityTypes().ToList();
    foreach (IMutableEntityType entityType in entityTypes)
    {
    //启用时间属性UTC格式
    if (_osharpDbOptions.DateTimeUtcFormatEnabled)
    {
        IEntityDateTimeUtcConversion utcConversion = _serviceProvider.GetService<IEntityDateTimeUtcConversion>();
        utcConversion.Convert(entityType);
    }
    // ...
    }
gmf520 commented 3 years ago

基于 #212 的改进,对此功能进行重构

添加接口IEntityBatchConfiguration的实现:

    /// <summary>
    /// 配置实体的时间属性的Utc时间转换,在数据库中保存Utc时间,在代码运行时使用当前时区的时间
    /// </summary>
    public class PropertyUtcDateTimeConfiguration : IEntityBatchConfiguration
    {
        private readonly IEntityManager _entityManager;
        private readonly OsharpOptions _osharpOptions;
        private readonly ValueConverter<DateTime, DateTime> _dateTimeConverter;
        private readonly ValueConverter<DateTime?, DateTime?> _nullableDateTimeConverter;

        /// <summary>
        /// 初始化一个<see cref="PropertyUtcDateTimeConfiguration"/>类型的新实例
        /// </summary>
        public PropertyUtcDateTimeConfiguration(IServiceProvider provider)
        {
            _entityManager = provider.GetService<IEntityManager>();
            _osharpOptions = provider.GetOSharpOptions();

            _dateTimeConverter = new ValueConverter<DateTime, DateTime>(
                local => local.ToUniversalTime(),
                utc => utc.ToLocalTime());
            _nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
                local => local.HasValue ? local.Value.ToUniversalTime() : local,
                utc => utc.HasValue ? utc.Value.ToLocalTime() : utc);
        }

        /// <summary>
        /// 配置指定的<see cref="IMutableEntityType"/>
        /// </summary>
        /// <param name="modelBuilder">模型构建器</param>
        /// <param name="mutableEntityType">实体的<see cref="IMutableEntityType"/>类型</param>
        public void Configure(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType)
        {
            foreach (IMutableProperty property in mutableEntityType.GetProperties())
            {
                if (property.ClrType == typeof(DateTime))
                {
                    property.SetValueConverter(_dateTimeConverter);
                }
                else if (property.ClrType == typeof(DateTime?))
                {
                    property.SetValueConverter(_nullableDateTimeConverter);
                }
            }
        }
    }

使用配置开关不妥,移除配置开关

由于此功能涉及数据库中的数据统一性,使用配置来开关,有可能造成数据不一致的问题,是否启用此功能,应在代码层面固定,而不应能轻易在配置文件中更改,故而移除OSharpDbContextOptions中的DateTimeUtcFormatEnabled配置项

加入DI来启用功能

services.AddSingleton<IEntityBatchConfiguration, PropertyUtcDateTimeConfiguration>();