lith 4 年之前
父节点
当前提交
bf94c69b50

+ 142 - 21
Library/Vit.Db.DbMng/Extendsions/IDbConnection_MsSqlExtensions.cs

@@ -141,37 +141,39 @@ if Exists(select top 1 * from sysObjects where Id=OBJECT_ID(N'sqler_temp_filebuf
 
         #region MsSql_WriteFileToDisk    
         /// <summary>
-        /// 写入文件到磁盘
+        /// 写入文件到数据库所在服务器
         /// </summary>
         /// <param name="conn"></param>
-        /// <param name="filePath"></param>
+        /// <param name="serverFilePath"></param>
         /// <param name="fileContent"></param>
-        public static void MsSql_WriteFileToDisk(this IDbConnection conn, string filePath, byte[] fileContent)
+        public static void MsSql_WriteFileToDisk(this IDbConnection conn, string serverFilePath, byte[] fileContent)
         {
-            string fmtFilePath = filePath + ".sqler.temp.fmt";
-
             conn.MsSql_Cmdshell(runCmd =>
             {
-
-                #region (x.1)把文件内容写入到临时表
-                conn.Execute(@"
-if Exists(select top 1 * from sysObjects where Id=OBJECT_ID(N'sqler_temp_filebuffer') and xtype='U')
-	drop table sqler_temp_filebuffer;
-select @fileContent as fileContent into sqler_temp_filebuffer;
-", new { fileContent }, commandTimeout: DapperConfig.CommandTimeout);
-                #endregion
+                string fmtFilePath = serverFilePath + ".MsDbMng.temp.fmt";
 
                 try
                 {
+                    DataTable cmdResult;
 
-                    #region (x.2)写入二进制文件到磁盘
-                    var log1 = runCmd("bcp \"select null union all select '0' union all select '0' union all select null union all select 'n' union all select null \" queryout \"" + fmtFilePath + "\" /T /c");
-                    Logger.Info("[sqler]-MsDbMng 写入文件到磁盘. 创建fmt文件,outlog: " + log1.Serialize());
 
-                    var log2 = runCmd("BCP \"SELECT fileContent FROM sqler_temp_filebuffer\" queryout \"" + filePath + "\" -T -i \"" + fmtFilePath + "\"");
-                    Logger.Info("[sqler]-MsDbMng 写入文件到磁盘.创建文件,outlog: " + log2.Serialize());
+                    //(x.1)创建fmt文件
+                    cmdResult = runCmd("bcp \"select null union all select '0' union all select '0' union all select null union all select 'n' union all select null \" queryout \"" + fmtFilePath + "\" /T /c");
+                    //Logger.Info("[MsDbMng] 写入文件到磁盘. 创建fmt文件,outlog: " + cmdResult.Serialize());
+              
 
-                    #endregion
+                    //(x.2)把文件内容写入到临时表
+                    conn.Execute(@"
+if Exists(select top 1 * from sysObjects where Id=OBJECT_ID(N'sqler_temp_filebuffer') and xtype='U')
+	drop table sqler_temp_filebuffer;
+select @fileContent as fileContent into sqler_temp_filebuffer;
+", new { fileContent }, commandTimeout: DapperConfig.CommandTimeout);              
+
+
+                    //(x.3)从临时表读取二进制内容到目标文件
+                    cmdResult = runCmd("BCP \"SELECT fileContent FROM sqler_temp_filebuffer\" queryout \"" + serverFilePath + "\" -T -i \"" + fmtFilePath + "\"");
+                    //Logger.Info("[MsDbMng] 写入文件到磁盘.创建文件,outlog: " + cmdResult.Serialize());
+             
 
                 }
                 finally
@@ -187,20 +189,139 @@ select @fileContent as fileContent into sqler_temp_filebuffer;
                     }
 
                     //(x.2)删除临时表
+                    try
+                    {
+                        conn.Execute(@"
+if Exists(select top 1 * from sysObjects where Id=OBJECT_ID(N'sqler_temp_filebuffer') and xtype='U')
+	drop table sqler_temp_filebuffer;"
+                        , commandTimeout: DapperConfig.CommandTimeout);
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.Error(ex);
+                    }
+                }
+
+            });
+
+        }
+        #endregion
+
+
+        #region MsSql_WriteFileToDisk    
+        /// <summary>
+        /// 分片写入文件到数据库所在服务器
+        /// </summary>
+        /// <param name="conn"></param>
+        /// <param name="serverFilePath"></param>
+        /// <param name="fileReader"></param>
+        /// <param name="sliceByte">每个文件分片的大小(byte)</param>
+        /// <returns>文件总大小(byte)</returns>
+        public static int MsSql_WriteFileToDisk(this SqlConnection conn, string serverFilePath, BinaryReader fileReader, int sliceByte = 100 * 1024 * 1024)
+        {
+
+            int fileSize = 0;
+
+            conn.MsSql_Cmdshell(runCmd =>
+            {
+                string serverTempFilePath = serverFilePath + ".MsDbMng.temp.tmp";
+                string fmtFilePath = serverFilePath + ".MsDbMng.temp.fmt";
+
+                try
+                {                  
+                    DataTable cmdResult;
+
+                    // (x.1)创建fmt文件
+                    cmdResult = runCmd("bcp \"select null union all select '0' union all select '0' union all select null union all select 'n' union all select null \" queryout \"" + fmtFilePath + "\" /T /c");
+                    //Logger.Info("[MsDbMng] 写入文件到磁盘. 创建fmt文件,outlog: " + cmdResult.Serialize());
+         
+
+
+                    #region (x.2)创建临时表
                     conn.Execute(@"
 if Exists(select top 1 * from sysObjects where Id=OBJECT_ID(N'sqler_temp_filebuffer') and xtype='U')
 	drop table sqler_temp_filebuffer;
+create table sqler_temp_filebuffer (fileContent varbinary(MAX) null);
 ", commandTimeout: DapperConfig.CommandTimeout);
+                    #endregion
+
+
+                    #region (x.3)分片写入文件                
+                  
+                    var fileContent = new byte[sliceByte];         
+                    while (true) 
+                    {
+                        int readLen = fileReader.Read(fileContent, 0, sliceByte);
+                        if (readLen == 0) break;
+                        if (readLen < sliceByte) 
+                        {
+                            fileContent = fileContent.AsSpan().Slice(0, readLen).ToArray();
+                        }
+
+                        //(x.x.1)把二进制数据写入到临时表
+                        conn.Execute(@"
+truncate table sqler_temp_filebuffer;
+insert into sqler_temp_filebuffer select @fileContent as fileContent;
+", new { fileContent = fileContent }, commandTimeout: DapperConfig.CommandTimeout);
+
+                        // (x.x.2)从临时表读取二进制内容保存到临时文件
+                        cmdResult = runCmd("BCP \"SELECT fileContent FROM sqler_temp_filebuffer\" queryout \"" + serverTempFilePath + "\" -T -i \"" + fmtFilePath + "\"");
+                        //Logger.Info("[MsDbMng] 写入文件到磁盘.创建文件,outlog: " + cmdResult.Serialize());
+
+
+                        // (x.x.3)吧临时文件内容追加到目标文件
+                        cmdResult = runCmd("Type \"" + serverTempFilePath + "\" >> \"" + serverFilePath + "\"");
+
+
+                        fileSize += readLen;
+                        if (readLen < sliceByte) break;
+                    }
+                    #endregion
+
+                }
+                finally
+                {
+                    //(x.1)删除fmt文件
+                    try
+                    {
+                        runCmd("del \"" + fmtFilePath + "\"");
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.Error(ex);
+                    }
+
+                    //(x.2)删除临时文件
+                    try
+                    {
+                        runCmd("del \"" + serverTempFilePath + "\"");
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.Error(ex);
+                    }
+
+                    //(x.3)删除临时表
+                    try
+                    {
+                        conn.Execute(@"
+if Exists(select top 1 * from sysObjects where Id=OBJECT_ID(N'sqler_temp_filebuffer') and xtype='U')
+	drop table sqler_temp_filebuffer;"
+                        , commandTimeout: DapperConfig.CommandTimeout);
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.Error(ex);
+                    }
                 }
 
             });
 
+            return fileSize;
         }
         #endregion
 
 
-
-
         #region MsSql_RunUseMaster
         public static T MsSql_RunUseMaster<T>(this IDbConnection conn, Func<IDbConnection, T> run)
         {

+ 32 - 18
Library/Vit.Db.DbMng/MsSql/MsSqlDbMng.cs

@@ -692,7 +692,7 @@ RESTORE DATABASE [Lit_Base1] FROM  DISK =@BakPath  WITH  FILE = 1,  RECOVERY ,
 
              */
         }
-        #endregion     
+        #endregion
 
 
 
@@ -702,41 +702,54 @@ RESTORE DATABASE [Lit_Base1] FROM  DISK =@BakPath  WITH  FILE = 1,  RECOVERY ,
         /// 远程还原数据库   
         /// </summary>
         /// <param name="bakFilePath">数据库备份文件的路径</param>
+        /// <param name="sliceMb">文件切片大小。默认:100,单位 MB。若指定小于备份文件大小的正数,则在传递文件到远程时进行分片传递。建议在备份文件过大时使用。</param>
         /// <returns>备份文件的路径</returns>
-        public string RestoreBak(string bakFilePath)
+        public void RestoreBak(string bakFilePath, int sliceMb = 100)
         {
             //(x.1)若数据库不存在,则创建数据库
             CreateDataBase();
 
-            #region (x.2)拼接在mdf同文件夹下的备份文件的路径
+            //(x.2)拼接在mdf同文件夹下的备份文件的路径
             var remote_mdfDirectory = Path.GetDirectoryName(GetMdfPath());
-            var remote_bakFilePath = Path.Combine(remote_mdfDirectory, "sqler_temp_" + dbName + ".bak");
-            #endregion
-
-
-            #region (x.3)把本地备份文件写入到远程
-            conn.MsSql_WriteFileToDisk(remote_bakFilePath,File.ReadAllBytes(bakFilePath));            
-            #endregion
+            var remote_bakFilePath = Path.Combine(remote_mdfDirectory, "sqler_temp_" + dbName + ".bak");         
 
-            #region (x.4)还原远程数据库            
+            #region    
             try
             {
+                #region (x.3)把本地备份文件传递到远程
+                var sliceByte = sliceMb * 1024 * 1024;
+                if (sliceByte <= 0 || new FileInfo(bakFilePath).Length <= sliceByte)
+                {
+                    //不分片,直接传递全部文件内容
+                    conn.MsSql_WriteFileToDisk(remote_bakFilePath, File.ReadAllBytes(bakFilePath));
+                }
+                else
+                {
+                    //分片传递文件
+                    using (var file = new FileStream(bakFilePath, FileMode.Open, FileAccess.Read))
+                    using (var bin = new BinaryReader(file))
+                    {
+                        conn.MsSql_WriteFileToDisk(remote_bakFilePath, bin, sliceByte: sliceByte);
+                    }
+                }
+                #endregion
+
+
+                //(x.4)还原远程数据库   
                 RestoreLocalBak(remote_bakFilePath);
+
             }
             finally
             {
                 //远程删除文件
                 conn.MsSql_DeleteFileFromDisk(remote_bakFilePath);
             }
-            #endregion
-
-
-            return bakFilePath;
+            #endregion 
 
         }
         #endregion
 
-        
+
 
 
 
@@ -750,15 +763,16 @@ RESTORE DATABASE [Lit_Base1] FROM  DISK =@BakPath  WITH  FILE = 1,  RECOVERY ,
         /// 远程还原数据库   
         /// </summary>
         /// <param name="filePath">数据库备份文件的路径。可为bak文件或zip文件</param>
+        /// <param name="sliceMb">文件切片大小。默认:100,单位 MB。若指定小于备份文件大小的正数,则在传递文件到远程时进行分片传递。建议在备份文件过大时使用。</param>
         /// <returns>备份文件的路径</returns>
-        public virtual void Restore(string filePath)
+        public virtual void Restore(string filePath, int sliceMb = 100)
         {
             var fileExtension = Path.GetExtension(filePath).ToLower();
 
             switch (fileExtension) 
             {
                 case ".zip": RestoreSqler(filePath);return;
-                case ".bak": RestoreBak(filePath); return;
+                case ".bak": RestoreBak(filePath, sliceMb: sliceMb); return;
             }
 
             throw new NotImplementedException($"不识别的备份文件类型。NotImplementedException from {nameof(Restore)} in {nameof(MsSqlDbMng)}.cs");

+ 10 - 1
Sqler/Module/Sqler/ConsoleCommand/SqlServerCommand.cs

@@ -65,6 +65,7 @@ namespace App.Module.Sqler.ConsoleCommand
         [Command("SqlServer.Restore")]
         [Remarks("远程还原数据库。参数说明:备份文件名称和路径指定其一即可")]
         [Remarks("-f[--force] 强制还原数据库。若指定此参数,则在数据库已经存在时仍然还原数据库;否则仅在数据库尚未存在时还原数据库。")]
+        [Remarks("-m[--sliceMb] 文件切片大小。传递文件到远程时可以分片传递。默认:100,单位 MB。若指定非正数则直接传递,不进行切片。")]
         [Remarks("-fn[--fileName] (可选)备份文件名称,备份文件在当前管理的备份文件夹中。例如 \"DbDev_2020-06-08_135203.bak\"")]
         [Remarks("-fp[--filePath] (可选)备份文件路径,例如 \"/root/docker/DbDev_2020-06-08_135203.bak\"")]
         [Remarks("-ConnStr[--ConnectionString] (可选)数据库连接字符串 例如 \"Data Source=.;Database=Db_Dev;UID=sa;PWD=123456;\"")]
@@ -87,7 +88,15 @@ namespace App.Module.Sqler.ConsoleCommand
 
             bool force = (ConsoleHelp.GetArg(args, "-f") ?? ConsoleHelp.GetArg(args, "--force")) != null;
 
-            SqlServerLogical.Restore(filePath: filePath, fileName: fileName);
+            int sliceMb = 100;
+            string strSliceMb = (ConsoleHelp.GetArg(args, "-m") ?? ConsoleHelp.GetArg(args, "--sliceMb"));
+            if (!string.IsNullOrEmpty(strSliceMb) && int.TryParse(strSliceMb, out var sliceMb_))
+            {
+                sliceMb = sliceMb_;
+            }
+      
+
+            SqlServerLogical.Restore(filePath: filePath, fileName: fileName, sliceMb: sliceMb);
 
             ConsoleHelp.Log("操作成功");
         }

+ 3 - 2
Sqler/Module/Sqler/Logical/SqlBackup/SqlServerBackup/SqlServerLogical.cs

@@ -174,7 +174,8 @@ namespace App.Module.Sqler.Logical.SqlBackup.SqlServerBackup
         /// <param name="filePath"></param>
         /// <param name="fileName"></param>
         /// <param name="force">若数据库已经存在,是否仍然还原</param>
-        public static void Restore(string filePath = null, string fileName = null, bool force = true)
+        /// <param name="sliceMb">文件切片大小。默认:100,单位 MB。若指定小于备份文件大小的正数,则在传递文件到远程时进行分片传递。建议在备份文件过大时使用。</param>
+        public static void Restore(string filePath = null, string fileName = null, bool force = true, int sliceMb = 100)
         {
             Logger.Info("[Sqler]MsSqlDbMng 远程还原数据库...");
             var startTime = DateTime.Now;
@@ -197,7 +198,7 @@ namespace App.Module.Sqler.Logical.SqlBackup.SqlServerBackup
                     }
                 }
 
-                dbMng.Restore(filePath);
+                dbMng.Restore(filePath, sliceMb);
             }
 
             var span = (DateTime.Now - startTime);