• FreeSql生产环境自动升级数据库解决方案


    项目场景:

    使用FreeSql,包含所有的ORM数据库,都会存在这样的问题。在codefirst模式下,根据代码自动更新数据库,都建议不要在生产环境使用。为什么呢?
    其实不建议使用,主要是根据代码自动生成数据时,极有可能会造成数据的丢失,比如修改字段类型,自动更新的结果可能并不是自己想的。
    但是有一些使用场景是需要在生产环境自动升级的,比如
    我们有一个CS客户端的产品,客户本地离线使用,客户本地部署,数据库也是本地数据库,版本从1000,迭代到了1100,中间发布了100个版本。这中间可能有多次数据库更改。我们的客户也可能遍布全国各地,版本也都不相同。客户如果没有新的需求,可能会一直使用当前旧版本,只有在有新的需求,或者想使用新的功能时,才会升级版本。所以升级的时间也是不确定的,升级要求客户安装新版软件,运行后自动升级。
    那就真的不能在生产环境使用呢?

    解决方案:

    概要描述:

    解决的思路其实就是自动升级,但是在判断需要升级时,才自动升级,同时升级前先备份数据库。

    具体流程
    程序内每次有数据库变更,发布版本时,修改程序内对应版本。比如最开始是1000,最新是1100
    在数据库增加SysConfig表,字段包含DbVer表示当前数据库版本号
    在数据库增加DbLog表,记录数据库升级日志,此项可选
    在首次安装时,检查数据库文件不存在,表示首次安装,首次安装时创建SysConfig表和DbLog表,同时更新SysConfig表DbVer为程序中记录版本号。增加DbLog表日志
    以后再次运行时,先获取SysConfig表DbVer,判断与程序中是否一致,
    如果数据库比程序中大,说明运行低版本的程序,根据情况可以禁止运行。也可以不同步数据库,继续运行,根据实际情况决定。如果对程序和数据库一致性要求比较高,可以禁止运行。
    如果数据库比程序小,说明数据库需要升级,此时先备份现有数据库,然后执行同步数据库。

    详细说明:

    直接上代码,比啥都清楚

    program.cs文件代码

    using Bonn.Helper;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using FreeSql.DataAnnotations;
    using WindowsClient.Model;
    using System.Reflection;
    
    namespace WindowsClient
    {
        static class Program
        {
            /// <summary>
            /// 客户数据库路径
            /// </summary>
            private static string CustDbPath = Application.StartupPath + $"\数据库\cust.db";
    
            /// <summary>
            /// 数据库ORM
            /// </summary>
            public static IFreeSql fsql;
    
            /// <summary>
            /// 服务器数据库版本
            /// </summary>
            private static int ServerDbVer = 1000;
    
    
            /// <summary>
            /// 应用程序的主入口点。
            /// </summary>
            [STAThread]
            static void Main()
            {
                try
                {
    
                    //数据库是否存在,用于插入初始数据,必须在freesql实例化前判断,因为实例化时会自动创建数据库
                    var custDbPathExists = File.Exists(CustDbPath);
    
                    //deebug自动同步实体结构到数据库,release手动同步
                    bool syncDbStructure = false;
    #if DEBUG
                    syncDbStructure = true;
    #endif
    
                    fsql = new FreeSql.FreeSqlBuilder()
                        .UseConnectionString(FreeSql.DataType.Sqlite, $@"Data Source={CustDbPath}; Pooling=true;Min Pool Size=1")
                        .UseAutoSyncStructure(syncDbStructure) //deebug自动同步实体结构到数据库,release手动同步
                        .UseMonitorCommand(cmd => Console.WriteLine($"线程:{cmd.CommandText}
    "))
                        .Build(); //请务必定义成 Singleton 单例模式
    
                    if(syncDbStructure)
                    {
                        //主要用于开发模式下,让数据库修改快速生效,不加此句时,只有在用到表时才会同步
                        fsql.CodeFirst.SyncStructure(GetTypesByTableAttribute());
                    }
    
                    if (custDbPathExists == false)
                    {
                        //数据库文件不存在,表示是首次安装
                        fsql.CodeFirst.SyncStructure(GetTypesByTableAttribute());
                        var sysConfig = new SysConfig();
                        sysConfig.DbVer = ServerDbVer;
                        var dbResult = fsql.Insert(sysConfig).ExecuteAffrows();
                        if (dbResult <= 0)
                            throw new Exception("初始数据库失败。");
    
                        var row = new DbLog();
                        row.DbVer = ServerDbVer;
                        fsql.Insert(row).ExecuteAffrows();
                    }
                    int localDbVer = fsql.Select<SysConfig>().First().DbVer;
                    if (localDbVer != ServerDbVer)
                    {
                        //数据库版本不一样,需要升级
                        //备份数据库
                        File.Copy(CustDbPath, Application.StartupPath + $"\数据库\cust{DateTime.Now:yyyyMMddHHmmss}.db");
                        //升级数据库
                        fsql.CodeFirst.SyncStructure(GetTypesByTableAttribute());
                        var row = new DbLog();
                        row.DbVer = ServerDbVer;
                        fsql.Insert(row).ExecuteAffrows();
                    }
    
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    Application.Run(new FrmMain());
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.ToString(), "出错啦", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
    
            public static Type[] GetTypesByTableAttribute()
            {
                List<Type> tableAssembies = new List<Type>();
                foreach (Type type in Assembly.GetAssembly(typeof(IEntity)).GetExportedTypes())
                {
                    foreach (Attribute attribute in type.GetCustomAttributes())
                    {
                        if (attribute is TableAttribute tableAttribute)
                        {
                            if (tableAttribute.DisableSyncStructure == false)
                            {
                                tableAssembies.Add(type);
                            }
                        }
                    }
                };
                return tableAssembies.ToArray();
            }
        }
    }
    
    

    SysConfig.cs

    using FreeSql.DataAnnotations;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace WindowsClient.Model
    {
        /// <summary>
        /// 
        /// </summary>
        [Table(Name = "sys_config")]
        public class SysConfig : IEntity
        {
            /// <summary>
            /// 主键
            /// </summary>
            [Column(Name = "id", IsIdentity = true, IsPrimary = true)]
            public int Id { get; set; }
    
            /// <summary>
            /// 主键
            /// </summary>
            [Column(Name = "dbVer")]
            public int DbVer { get; set; }
    
            /// <summary>
            /// 创建时间
            /// </summary>
            [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)]
            public DateTime CreateTime { get; set; }
    
            /// <summary>
            /// 修改时间
            /// </summary>
            [Column(ServerTime = DateTimeKind.Local, CanUpdate = true)]
            public DateTime UpdateTime { get; set; }
    
        }
    }
    
    

    DbLog.cs

    using FreeSql.DataAnnotations;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace WindowsClient.Model
    {
        /// <summary>
        /// 
        /// </summary>
        [Table(Name = "db_log")]
        public class DbLog : IEntity
        {
            /// <summary>
            /// 主键
            /// </summary>
            [Column(Name = "id", IsIdentity = true, IsPrimary = true)]
            public int Id { get; set; }
    
            /// <summary>
            /// 主键
            /// </summary>
            [Column(Name = "dbVer")]
            public int DbVer { get; set; }
    
            /// <summary>
            /// 创建时间
            /// </summary>
            [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)]
            public DateTime CreateTime { get; set; }
    
            /// <summary>
            /// 修改时间
            /// </summary>
            [Column(ServerTime = DateTimeKind.Local, CanUpdate = true)]
            public DateTime UpdateTime { get; set; }
        }
    }
    
    

    总结:

    以前是手写的SQL语句,现在用FreeSql确实方便多了。感觉FreeSql。
    以上方案有需要改进的,或者好的建议,希望大家评论留言。

  • 相关阅读:
    Java 读取大容量excel
    Linux 安装mysql
    Linux 配置nginx
    java 微信H5支付
    微信公众号授权登录两种方式
    Jsoup 获取页面返回的table中的内容
    Python Model执行迁移数据库失败
    java上传txt文件,出现中文乱码
    在Window环境下,使用Django shell 命令查询数据库
    Java模拟form表单提交普通参数和文件
  • 原文地址:https://www.cnblogs.com/zhupengfei/p/14137128.html
Copyright © 2020-2023  润新知