ASP.NET?Core調(diào)用?Web?API?備份數(shù)據(jù)庫(kù)的詳細(xì)過(guò)程
老周不經(jīng)意間翻了一下博客列表,上一篇水文竟然在 一個(gè)月前。啊,大海啊,全是水;時(shí)間啊,你跑得真快!過(guò)了一個(gè)月老周竟沒(méi)感覺(jué)出來(lái),可是這一個(gè)月里,好像啥事也沒(méi)干成,就改了一下幾個(gè)舊項(xiàng)目。也許是現(xiàn)在大環(huán)境真的不好,新項(xiàng)目不好找。新的活有是有,比較簡(jiǎn)單,卻很奇怪,比那貨難的項(xiàng)目都做過(guò),偏偏這貨沒(méi)頭緒。這東西需求就是畫(huà)圖——是用程序來(lái)畫(huà)圖,類似甘特圖。莫名其妙的是,這活會(huì)卡在窗口排版上。按照需求,這貨窗口特多。點(diǎn)一下這里,彈一個(gè)對(duì)話框出來(lái)可以修改;點(diǎn)一下那里,又要彈一個(gè)出來(lái)允許修改;右鍵單擊一下,彈出上百個(gè)選項(xiàng)的菜單(Check 模式),簡(jiǎn)直離譜,那么多指標(biāo)項(xiàng),我都懷疑他們老板根本不會(huì)去看的,但他們非要做全面。更鬧心的是主窗口,想想現(xiàn)在的顯示屏分辨率又高又大,一個(gè)窗口全屏放在那里,可上面又沒(méi)幾個(gè)控件,70% 的地方就是畫(huà)進(jìn)度圖,每幾秒刷新一下。雖然簡(jiǎn)單易用最好,但這窗口是看著空洞了一些。免得那些不懂編程的人說(shuō)老周這項(xiàng)目根本沒(méi)干啥活,老周還打算給它弄個(gè)高清《美少女戰(zhàn)士》或《劉姥姥進(jìn)大觀園》當(dāng)背景圖,這樣看起來(lái)就不空洞了。
今天咱們聊一個(gè)很單的主題,寫(xiě)一個(gè) Web API,客戶端可以調(diào)用它來(lái)備份 SQL Server 數(shù)據(jù)庫(kù)。不知道大伙伴們有沒(méi)有做過(guò)這活。相信做過(guò)的人會(huì)比老周更明白,畢竟,老熟人都知道,老周有兩大弱項(xiàng):SQL、匯編。匯編呢,是學(xué)生時(shí)代沒(méi)好好學(xué),想當(dāng)年很輕松地就拿下了二級(jí)C++,偏就沒(méi)學(xué)會(huì)匯編;而 SQL 呢,本來(lái)就學(xué)得一般,再加上用得少,忘得差不多了,所以別人給老周安排的項(xiàng)目基本不包括寫(xiě) SQL 的,最終導(dǎo)致 SQL 方面越來(lái)越弱。
EF Core 不僅能用 LINQ 和實(shí)體類型配合操作,確實(shí)能讓你在80%的情況下不用寫(xiě)SQL語(yǔ)句,但,為了靈活,EF Core 和早期 EF 和 ADO.NET 一樣,是可以直接執(zhí)行 SQL 語(yǔ)句的。這意味著,備份和還原數(shù)據(jù)庫(kù)不在話下。
這個(gè)功能的實(shí)現(xiàn)并不難,但有兩小坑,老周接下來(lái)會(huì)慢慢講。
備份數(shù)據(jù)庫(kù)用的是 BACKUP DATABASE 語(yǔ)句,比如這樣
BACKUP DATABASE <你的數(shù)據(jù)庫(kù)名> TO DISK = 'D:\backups\dbs.bak'
DISK 后的表達(dá)式不一定指定磁盤(pán),更多時(shí)候是備份的文件名。如果還要備份日志,可以接著執(zhí)行 BACKUP LOG 語(yǔ)句。
BACKUP LOG <數(shù)據(jù)庫(kù)名> TO DISK = 'E:\WrtMsn\oodo-log.bak'
如果要把備份放到另一臺(tái)服務(wù)器上,可以用共享路徑。
BACKUP DATABASE <數(shù)據(jù)庫(kù)名> TO DISK = '\\GaoXServer\shares\team\abc.bak'
這里就有第一個(gè)坑——權(quán)限,不管是你的程序進(jìn)程還是 MSSQL 進(jìn)程,有些目錄是沒(méi)有寫(xiě)入的權(quán)限的。比較懶的做法是在放置備份的目錄上給個(gè) everyone 的完全控制權(quán)限。老周不推薦這樣,太不厚道了。若是不太好確定用哪個(gè)用戶(畢竟有時(shí)候不一定是某個(gè)用戶,而是系統(tǒng)服務(wù)),Windows 里面有一個(gè)好用的名稱,叫Authenticated Users,看字面意思就已經(jīng)過(guò)驗(yàn)證的用戶,就不是單指某個(gè)用戶了。這個(gè)比 everyone 靠譜多了,起碼匿名的不允許。
簡(jiǎn)單說(shuō)說(shuō)操作,在目錄上右擊,打開(kāi)“屬性”窗口,切換到“安全”頁(yè)。點(diǎn)擊下面的“高級(jí)”按鈕。
點(diǎn)擊“添加”按鈕。
點(diǎn)擊“選擇主體”,找到 Authenticated Users 并添加。最起碼給予寫(xiě)入和修改權(quán)限。
然后一路確定、應(yīng)用即可。
============================================================================================
好了,理論知識(shí)完畢,下面可以動(dòng)手了。
先建個(gè)庫(kù)用來(lái)測(cè)試。這里老周建了個(gè)實(shí)體,名為 Movie,表示一部大片。
public class Movie { /// <summary> /// 編號(hào) /// </summary> public int Id { get; set; } /// <summary> /// 電影標(biāo)題 /// </summary> public string? Subject { get; set; } /// <summary> /// 導(dǎo)演是誰(shuí) /// </summary> public string? Director { get; set; } /// <summary> /// 哪年上映的 /// </summary> public int? Year { get; set; } /// <summary> /// 講了啥故事 /// </summary> public string? Desc { get; set; } }
接下來(lái)寫(xiě) DbContext。
public class TestDBContext:DbContext { public TestDBContext(DbContextOptions<TestDBContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 主鍵 modelBuilder.Entity<Movie>().HasKey(x => x.Id); // 影片名為必填 modelBuilder.Entity<Movie>().Property(x => x.Subject).IsRequired(); // 填充初始數(shù)據(jù) modelBuilder.Entity<Movie>().ToTable("t_movies").HasData( new Movie { Id = 1, Subject = "狗二總裁", Year = 2026, Director = "大頭蘇", Desc = "二五仔二次創(chuàng)業(yè)的勵(lì)志故事" }, new Movie { Id = 2, Subject = "子夜實(shí)驗(yàn)室", Year = 2025, Director = "丁小丁", Desc = "某大學(xué)的實(shí)驗(yàn)室總是在子夜時(shí)分莫名發(fā)生火災(zāi),校方懷疑有人惡意縱火,于是,學(xué)校成立專項(xiàng)小組進(jìn)行調(diào)查……" } ); // 下面這行可以省略,Id 屬性默認(rèn)是自增長(zhǎng)標(biāo)識(shí) //modelBuilder.Entity<Movie>().Property(c => c.Id).ValueGeneratedOnAdd(); } public DbSet<Movie> Movies { get; set; } }
重寫(xiě)OnModelCreating 方法是對(duì)模型做一些自定義設(shè)置,如果你只需保留默認(rèn),可以不重寫(xiě)此方法。由于表示主鍵的屬性名為 Id,EF Core 會(huì)自動(dòng)認(rèn)為是主鍵,且啟用自增長(zhǎng)標(biāo)識(shí)。上面代碼老周用 ToTable 方法映射到的數(shù)據(jù)表名 t_movies。如果你要求表名和實(shí)體類名一樣,那可以忽略。
另外,代碼還調(diào)用了 HasData 方法,其用途是插入一些初始數(shù)據(jù)。如果你的數(shù)據(jù)表的初始狀態(tài)允許空白,可以忽略。如果要插入種子數(shù)據(jù)(即初始數(shù)據(jù)),這里有第二個(gè)坑,一定要顯式地指定 Id 屬性的值,這里比較特殊。因?yàn)檫@是種子數(shù)據(jù),必須保證每個(gè)字段的值是靜態(tài)的,也就是說(shuō),不管你的程序在哪臺(tái)機(jī)器上運(yùn)行,得保證插入的數(shù)據(jù)是相同的。Id 字段雖然是自增長(zhǎng)的,可你無(wú)法保證實(shí)際使用時(shí)數(shù)據(jù)庫(kù)會(huì)從1開(kāi)始增長(zhǎng),也不保證步長(zhǎng)值一定是1。說(shuō)不定人家是 100、101、102 呢。正因?yàn)槿绱?,如果你忽?Id 的值就會(huì)拋出異常。有一種方案就是使用負(fù)值,這個(gè)不如硬編碼一個(gè)整數(shù)值保險(xiǎn)一些。
完工后,要在 ASP.NET Core 應(yīng)用程序的服務(wù)容器中注冊(cè)。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); // 配置數(shù)據(jù)庫(kù) string? connectionStr = builder.Configuration.GetConnectionString("prj_cns"); builder.Services.AddDbContext<TestDBContext>(ob => { ob.UseSqlServer(connectionStr); }); var app = builder.Build();
數(shù)據(jù)庫(kù)的連接字符串在配置文件中(appsettings.json)。
{ "Logging": { …… } }, "AllowedHosts": "*", "ConnectionStrings": { "prj_cns": "Data Source=.\\SQLTEST;Initial Catalog=Demo;Integrated Security=True;Persist Security Info=False;Pooling=False;Encrypt=True;Trust Server Certificate=True" } }
在配置文件中使用 ConnectionStrings 節(jié)點(diǎn)有特殊含義,把數(shù)據(jù)庫(kù)連接字符串放在此節(jié)點(diǎn)下,代碼中獲取時(shí)可以簡(jiǎn)化一些。
builder.Configuration.GetConnectionString("prj_cns");
就是 GetSection("ConnectionStrings")["prj_cns"] 的簡(jiǎn)化版。
======================================================================================
最后要實(shí)現(xiàn)的是 Web API。對(duì)于有明確的模塊功能的代碼我們盡量用 MVC Controller 去實(shí)現(xiàn),Mini-API 只適合沒(méi)啥模塊化的簡(jiǎn)單代碼,也避免把 Main 方法的代碼搞得又長(zhǎng)又臭。
public class TestController : Controller { private readonly TestDBContext _db; public TestController(TestDBContext db) { _db = db; } [HttpGet("/")] public ActionResult Index() { _db.Database.EnsureCreated(); return View("~/Views/home.cshtml", _db.Movies.ToArray()); } [HttpPost("/demo/new")] public async Task<JsonResult> NewData([FromBody]Movie mvi) { bool hasany = await _db.Movies.AnyAsync(m => m.Subject!.Equals(mvi.Subject)); if(hasany) { return Json( new { Code = 2, Message = "此電影已存在" } ); } await _db.Movies.AddAsync(mvi); int i = await _db.SaveChangesAsync(); if(i <= 0) { return Json(new { Code = 3, Message = "添加失敗" }); } return Json(new { Code = 0, Message = "OK" }); } [HttpGet("/demo/getall")] public IEnumerable<Movie> GetAllMovies() { return _db.Movies.ToArray(); } [HttpPost("/demo/backup")] public ActionResult Backup([FromBody]string filename) { // 取得當(dāng)前目錄 IWebHostEnvironment env = HttpContext.RequestServices.GetRequiredService<IWebHostEnvironment>(); string curDir = env.ContentRootPath; string bkfilePath = Path.Combine(curDir, filename); string dbname = _db.Database.GetDbConnection().Database; if (System.IO.File.Exists(bkfilePath)) { System.IO.File.Delete(bkfilePath); } string sql = $"BACKUP DATABASE {dbname} TO DISK = '{bkfilePath}'"; try { // 不必理會(huì)返回值 _ = _db.Database.ExecuteSqlRaw(sql); } catch { return Json(new { Code = 1, Message = "備份失敗" }); } return Json(new { Code = 0, Message = "OK" }); } }
其他代碼你可以不看,重點(diǎn)關(guān)注 Backup 方法即可,這才是核心。數(shù)據(jù)上下文(我們從 DbContext 類派生了)有一個(gè) Database 屬性,可以對(duì)數(shù)據(jù)庫(kù)進(jìn)行額外的操作,比如EnsureCreated 方法可以檢查數(shù)據(jù)庫(kù)是否存在,不存在就自動(dòng)創(chuàng)建;EnsureDeleted 方法可以檢查數(shù)據(jù)庫(kù)是否已存在,若是,刪除它;你還可以用EnsureDeleted 方法啟動(dòng)一個(gè)事務(wù)。咱們用到的是ExecuteSqlRaw 方法,它的功能是執(zhí)行原始 SQL 語(yǔ)句。何謂“原始”?就是你給啥SQL就是啥,不做參數(shù)化處理,也不過(guò)濾SQL注入攻擊。所以,這個(gè)方法你要在安全的情況下調(diào)用。本例的代碼調(diào)用是安全的,因?yàn)橹皇菆?zhí)行 BACKUP 語(yǔ)句,客戶端不能傳入其他無(wú)關(guān)的數(shù)據(jù)——僅僅需要提供備份的文件名。出于安全考慮,你甚至可以不用客戶端提供文件名,而是直接生成(用日期時(shí)間 + GUID 方式就行)。 當(dāng)然了,在實(shí)際使用中,你應(yīng)該對(duì) API 的調(diào)用做一下驗(yàn)證,比如,客戶端必須傳一個(gè)有效的 token 表明身份等。
由于執(zhí)行 BACKUP 語(yǔ)句不屬于常規(guī)的“增刪改查”操作,無(wú)論執(zhí)行成功還是失敗,返回的整數(shù)值都是負(fù)值。因此,咱們不能依據(jù)返回的數(shù)值來(lái)判斷是否備份成功。不過(guò),倒可以用 try 語(yǔ)句塊來(lái)簡(jiǎn)單判斷。因?yàn)槿绻麄浞莩鲥e(cuò)會(huì)拋異常。把ExecuteSqlRaw 方法調(diào)用寫(xiě)在 try 語(yǔ)句塊中,若拋異常,說(shuō)明備份過(guò)程不順利。
為了讓客戶端能方便管理數(shù)據(jù)維護(hù),你還可以加一個(gè) API,讓客戶端下載備份的文件。不過(guò),如果操作的環(huán)境不夠安全,或沒(méi)有有效的安全措施,有泄露數(shù)據(jù)庫(kù)的風(fēng)險(xiǎn)。
============================================================================================
本示例備份的文件是放在應(yīng)用程序目錄中的,所以,你得事先給程序目錄設(shè)置一下權(quán)限,允許寫(xiě)入文件(參考前文)。這是為了程序部署方便,如果服務(wù)器環(huán)境允許,可以在服務(wù)器上選擇一個(gè)專用的目錄來(lái)存放備份??梢园崖窂脚渲玫?appsettings.json 文件中,程序中直接讀取,這樣你在部署項(xiàng)目后可以手動(dòng)在 appsettings.json 文件中編輯正確的目錄。
{ …… "AllowedHosts": "*", "ConnectionStrings": { …… }, "backup_dir": "D:\\backups" }
修改程序代碼,從 backup_dir 配置項(xiàng)讀取目錄。
// 取得當(dāng)前目錄 IWebHostEnvironment env = HttpContext.RequestServices.GetRequiredService<IWebHostEnvironment>(); // 讀取配置 IConfiguration config = HttpContext.RequestServices.GetRequiredService<IConfiguration>(); // 如果找不到配置的目錄,那就放在應(yīng)用程序目錄下 string bkfilePath = Path.Combine(bkDir, filename); string dbname = _db.Database.GetDbConnection().Database; if (System.IO.File.Exists(bkfilePath)) { System.IO.File.Delete(bkfilePath); } string sql = $"BACKUP DATABASE {dbname} TO DISK = '{bkfilePath}'"; ……
你不能保證配置 backup_dir 一定有效,如果沒(méi)有配置,就把備份文件放在應(yīng)用程序目錄下。
在運(yùn)行程序后,你要訪問(wèn)一次根 URL(/),目的是讓 EF Core 自動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)。如果在 VS 中調(diào)試運(yùn)行,默認(rèn)會(huì)自動(dòng)打?yàn)g覽器的。
創(chuàng)建的數(shù)據(jù)庫(kù)如下:
現(xiàn)在,你可以用任何方式,任何 API 測(cè)試工具來(lái)驗(yàn)證一下,能不能備份。不想用第三方工具,可以用.NET 官方的 httprepl 工具,安裝命令:
dotnet tool install -g microsoft.dotnet-httprepl
安裝好后,在 cmd 中直接輸入 httprepl 就能進(jìn)入對(duì)話狀態(tài)。是的,交互式的。
為了后面在 POST JSON 數(shù)據(jù)時(shí)方便編輯,咱們?cè)O(shè)置一個(gè)默認(rèn)的文本編譯器。免得折騰,你可以直接用 VS Code。
pref set editor.command.default "C:\Users\Admin\Softs\VSCode\Code.exe" pref set editor.command.default.argument "-w"
第一條命令是設(shè)置 VS Code 的路徑,注意,是 VSC 的可執(zhí)行文件路徑。
第二條命令是設(shè)置命令行參數(shù) -w,此參數(shù)是讓 VSC 等待打開(kāi)的文件順利保存并關(guān)閉才退出進(jìn)程。因?yàn)?httprepl 工具在啟動(dòng) VSC 時(shí)會(huì)創(chuàng)建一個(gè)臨 時(shí)文件,并讓 VSC 打開(kāi)來(lái)編輯。
好,現(xiàn)在咱們測(cè)試。
1、運(yùn)行示例程序。
2、CMD中進(jìn)入 httprepl 會(huì)話。
3、連接根URL。
connect http://localhost:5276
4、調(diào)用 /demo/backup 接口。
post /demo/backup -h content-type=application/json
-h 指定HTTP頭。
5、此時(shí),VSC 會(huì)啟動(dòng),我們編輯一下。
注意這里,不用輸入大括號(hào),直接輸入 Javascript 字符串就行,因?yàn)?filename 參數(shù)不是復(fù)雜對(duì)象,而是一個(gè)基礎(chǔ)類型。
6、保存,關(guān)閉 VSC。
7、回到 CMD 窗口,就會(huì)看到備份成功的響應(yīng)。
到備份目錄下,就能看到剛備份的文件了。大小約 3.46 MB 。
要驗(yàn)證備份是否真有效,可以把這個(gè)文件還原回 SQL Server,如果還原后,看到數(shù)據(jù),說(shuō)明備份是沒(méi)有問(wèn)題的。
好了,今天就水到這里了。
到此這篇關(guān)于ASP.NET Core調(diào)用 Web API 備份數(shù)據(jù)庫(kù)的文章就介紹到這了,更多相關(guān)ASP.NET Core調(diào)用 Web API 備份數(shù)據(jù)庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- IIS上部署Asp.net core Webapi的實(shí)現(xiàn)步驟
- IIS上部署你的ASP.NET?Core?Web?Api項(xiàng)目及Swagger(圖文)
- Asp.net core Web Api配置swagger中文的實(shí)現(xiàn)
- ASP.NET?Core在WebApi項(xiàng)目中使用Cookie
- 詳解如何在ASP.NET Core Web API中以三種方式返回?cái)?shù)據(jù)
- asp.net core webapi文件上傳功能的實(shí)現(xiàn)
- 詳解ASP.NET Core Web Api之JWT刷新Token
- asp.net 備份和恢復(fù)數(shù)據(jù)庫(kù)的方法示例
- ASP.NET下備份與還原數(shù)據(jù)庫(kù)代碼
相關(guān)文章
ASP.NET Internet安全Forms身份驗(yàn)證方法
安全性是 ASP.NET Web 應(yīng)用程序中一個(gè)非常重要的方面,它涉及內(nèi)容非常廣泛,不能在一篇文章內(nèi)說(shuō)明所有的安全規(guī)范,本文講述如何利用IIS以及Forms 身份驗(yàn)證構(gòu)建安全的 ASP.NET 應(yīng)用程序,它是目前被使用最多最廣的驗(yàn)證/授權(quán)方式.2009-12-12巧用ASP.NET預(yù)編譯Web應(yīng)用程序規(guī)避調(diào)用延遲的方法
ASP.NET 1.x的開(kāi)發(fā)人員常常聽(tīng)到用戶抱怨首次調(diào)用應(yīng)用程序的時(shí)候會(huì)碰到初始化延遲。畢竟,初次請(qǐng)求會(huì)引發(fā)一個(gè)系列過(guò)程,包括運(yùn)行庫(kù)初始化、分析、把ASPX頁(yè)面編譯成中間語(yǔ)言、把方法即時(shí)編譯成本地代碼等等。2011-08-08HttpRequest Get和Post調(diào)用其他頁(yè)面的方法
HttpRequest Get和Post調(diào)用其他頁(yè)面的方法,需要的朋友可以參考一下2013-03-03.net重啟iis線程池和iis站點(diǎn)程序代碼分享
服務(wù)器監(jiān)控,一定時(shí)間內(nèi)或者iis異常就需要重啟線程池和站點(diǎn),下面我們用代碼來(lái)做這個(gè)功能2013-12-12詳解ASP.NET Core應(yīng)用中如何記錄和查看日志
本篇文章主要介紹了ASP.NET Core應(yīng)用中如何記錄和查看日志,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-12-12ASP.NET MVC結(jié)合JavaScript登錄、校驗(yàn)和加密
這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC結(jié)合JavaScript登錄、校驗(yàn)和加密的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08.Net實(shí)現(xiàn)上傳圖片按比例自動(dòng)縮小或放大的方法
這篇文章主要介紹了.Net實(shí)現(xiàn)上傳圖片按比例自動(dòng)縮小或放大的方法,實(shí)例內(nèi)容簡(jiǎn)潔功能實(shí)用,需要的朋友可以參考下2014-09-09