ASP.NET MVC 4 - 新功能 DB Migration [實作系列]

在筆者介紹這麼 MVC 4 新功能之中,還有一個新功能 DB Migration ,先前在 TW MVC 的投影片中筆者有稍微的介紹過,其實這個功能也不是只有在 MVC 內才可以使用,比如說當中所使用的 Code First,而所謂的 DB Migration 其實就是指在 Code First 中,原先在 Model 中如果增加欄位 DB 可能需要重建,在新增的 DB Migration 便是要解決這個問題

在筆者介紹這麼 MVC 4 新功能之中,還有一個新功能 DB Migration ,先前在 TW MVC 的投影片中筆者有稍微的介紹過,其實這個功能也不是只有在 MVC 內才可以使用,比如說當中所使用的 Code First,而所謂的 DB Migration 其實就是指在 Code First 中,原先在 Model 中如果增加欄位 DB 可能需要重建,在新增的 DB Migration 便是要解決這個問題。

image

在了解 DB Migration 如何解決這個問題前,我們先來看看新的 Code First 提供的功能。

在 MVC 4 裡使用 Code-First ,建議使用 BASIC 專案,這麼一來就不需要在加入jQuery & jQuery UI & jQuery Validation 等套件。如下圖:

image

註:在 Visual Studio 2012 Ultemate RTM 版中翻作 [基本],老實說筆者不太習慣XD

 

1. 在 Models 資料夾中加入 Products 類別 與 ProductContext 類別。

在 Product 類別先提供 [id] 與 [Name] 這兩個欄位,稍後再試著加入其他欄位。

   1:      public class Products
   2:      {
   3:          public int id { get; set; }
   4:          public string Name { get; set; }
   5:      }

 

要注意的是 ProductContext 類別一定要繼承 DbContext 類別,他如同 Entity Context 一樣,包含對資料庫處理的相關實作。待會會透過它來產生 CRUD 的 View Template 。而對 DbContext 而言,它的屬性就是 Model 的部分,屬性的部分必須使用 DbSet<自己定義的類別> 的類別來設定,方式如下:

image

因為目前我們只有一個 Products 類別,所以在 ProductContext 類別中只有一個 DbSet 屬性,因此有增加 Model 就記得增加上去以便 Code-First 自動產生 Table。

 

2.  加入 [具有讀取/寫入動作和檢視、使用 Entity Framework 的 MVC 控制器]

因為目前只有一個 Products 類別,所以這個控制器的模型類別我們就選擇 Products 類別,如下:

image

資料內容類別當然就是 ProductContext 類別了。

註:在進行作此動作之前須先編譯。

 

上述動作完成後,IDE 工具會自動產生 ProductsController.cs 檔案與 Index/Create/Delete/Edit/Details 等 CRUD 必備的相關的 View 檢視。

ProductsController.cs 的內容中也是的在 MVC 3 就已經提供 EDM 基本範本,程式碼如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Data;
   4:  using System.Data.Entity;
   5:  using System.Linq;
   6:  using System.Web;
   7:  using System.Web.Mvc;
   8:  using Mvc4DatabaseMigrationApp1.Models;
   9:  
  10:  namespace Mvc4DatabaseMigrationApp1.Controllers
  11:  {
  12:      public class ProductsController : Controller
  13:      {
  14:          private ProductContext db = new ProductContext();
  15:  
  16:          //
  17:          // GET: /Products/
  18:  
  19:          public ActionResult Index()
  20:          {
  21:              return View(db.products.ToList());
  22:          }
  23:  
  24:          //
  25:          // GET: /Products/Details/5
  26:  
  27:          public ActionResult Details(int id = 0)
  28:          {
  29:              Products products = db.products.Find(id);
  30:              if (products == null)
  31:              {
  32:                  return HttpNotFound();
  33:              }
  34:              return View(products);
  35:          }
  36:  
  37:          //
  38:          // GET: /Products/Create
  39:  
  40:          public ActionResult Create()
  41:          {
  42:              return View();
  43:          }
  44:  
  45:          //
  46:          // POST: /Products/Create
  47:  
  48:          [HttpPost]
  49:          public ActionResult Create(Products products)
  50:          {
  51:              if (ModelState.IsValid)
  52:              {
  53:                  db.products.Add(products);
  54:                  db.SaveChanges();
  55:                  return RedirectToAction("Index");
  56:              }
  57:  
  58:              return View(products);
  59:          }
  60:  
  61:          //
  62:          // GET: /Products/Edit/5
  63:  
  64:          public ActionResult Edit(int id = 0)
  65:          {
  66:              Products products = db.products.Find(id);
  67:              if (products == null)
  68:              {
  69:                  return HttpNotFound();
  70:              }
  71:              return View(products);
  72:          }
  73:  
  74:          //
  75:          // POST: /Products/Edit/5
  76:  
  77:          [HttpPost]
  78:          public ActionResult Edit(Products products)
  79:          {
  80:              if (ModelState.IsValid)
  81:              {
  82:                  db.Entry(products).State = EntityState.Modified;
  83:                  db.SaveChanges();
  84:                  return RedirectToAction("Index");
  85:              }
  86:              return View(products);
  87:          }
  88:  
  89:          //
  90:          // GET: /Products/Delete/5
  91:  
  92:          public ActionResult Delete(int id = 0)
  93:          {
  94:              Products products = db.products.Find(id);
  95:              if (products == null)
  96:              {
  97:                  return HttpNotFound();
  98:              }
  99:              return View(products);
 100:          }
 101:  
 102:          //
 103:          // POST: /Products/Delete/5
 104:  
 105:          [HttpPost, ActionName("Delete")]
 106:          public ActionResult DeleteConfirmed(int id)
 107:          {
 108:              Products products = db.products.Find(id);
 109:              db.products.Remove(products);
 110:              db.SaveChanges();
 111:              return RedirectToAction("Index");
 112:          }
 113:  
 114:          protected override void Dispose(bool disposing)
 115:          {
 116:              db.Dispose();
 117:              base.Dispose(disposing);
 118:          }
 119:      }
 120:  }

 

 

3. 執行程式

在這一版的 Code-First 中,這樣程式就可以執行了!是的?你沒看錯!這樣程式就可以執行了,我們剛才其實什麼事其實也都沒做,只是加入兩個類別與一個控制器而已,程式就可以跑了,可以新增資料、刪除資料,如下:

image

讀者這時候一定會好奇,那這時候資料是存到哪裡去了?

如果開啟 SQL Server Management Studio 並連線到到預設的 SQLEXPRESS 執行各體就會發現一個以 [專案名稱+ProductContext] 的 SQL Express 的資料庫,是的!預設存到那裏去了,那怎麼改為我們要的位置呢?這個稍後會說明,我們先測試如何新增欄位吧。

 

4. 對 Products 類別加入新的欄位 [Type]

當您加入欄位後直接執行時,可能就會得到一個 InvalidOperationException 的錯誤訊息。

image

 

5. 透過Package Manager Console下Cmdlet指令來完成新增欄位的動作

首先須先開啟Migration功能,如下執行 Enable-Migrations 的 Cmd-Let 命令:

image

在這個時候,MVC 專案裡會出現一個 Migration 資料夾,並增加一個 201208030233213_InitialCreate.cs & Configuration.cs 檔案,InitialCreate 的這個檔案主要是提供到時候系統要自動對 DB 移除資料表與降級 (也就是DropTable)的呼叫內容程式碼,這個自動產生的程式如下:

   1:  namespace Mvc4DatabaseMigrationApp1.Migrations
   2:  {
   3:      using System;
   4:      using System.Data.Entity.Migrations;
   5:      
   6:      public partial class InitialCreate : DbMigration
   7:      {
   8:          public override void Up()
   9:          {
  10:              CreateTable(
  11:                  "dbo.Products",
  12:                  c => new
  13:                      {
  14:                          id = c.Int(nullable: false, identity: true),
  15:                          Name = c.String(),
  16:                      })
  17:                  .PrimaryKey(t => t.id);
  18:              
  19:          }
  20:          
  21:          public override void Down()
  22:          {
  23:              DropTable("dbo.Products");
  24:          }
  25:      }
  26:  }

 

而 Configuration.cs 定義的主要是這個 Code-First 的相關設定,如自動產生欄位,因為現在我們是手動加入欄位,在 DB Migration 的新增功能中包含了自動加入欄位的功能,這個就是在 Configuration.cs 中設定的,Configuration.cs 的內容如下:

   1:  namespace Mvc4DatabaseMigrationApp1.Migrations
   2:  {
   3:      using System;
   4:      using System.Data.Entity;
   5:      using System.Data.Entity.Migrations;
   6:      using System.Linq;
   7:  
   8:      internal sealed class Configuration : DbMigrationsConfiguration<Mvc4DatabaseMigrationApp1.Models.ProductContext>
   9:      {
  10:          public Configuration()
  11:          {
  12:              AutomaticMigrationsEnabled = false;
  13:          }
  14:  
  15:          protected override void Seed(Mvc4DatabaseMigrationApp1.Models.ProductContext context)
  16:          {
  17:              //  This method will be called after migrating to the latest version.
  18:  
  19:              //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
  20:              //  to avoid creating duplicate seed data. E.g.
  21:              //
  22:              //    context.People.AddOrUpdate(
  23:              //      p => p.FullName,
  24:              //      new Person { FullName = "Andrew Peters" },
  25:              //      new Person { FullName = "Brice Lambson" },
  26:              //      new Person { FullName = "Rowan Miller" }
  27:              //    );
  28:              //
  29:          }
  30:      }
  31:  }

 

如上 Configuration 類別中,自動產生欄位 AutomaticMigrationsEnabled 的設定預設值是 false 的。

接著再執行 Add-Migration AddType 的Cmd-Let 命令,表示真的加入此欄位,如下圖:

image

這個時候在 Migration 的資料夾下面又會出現一個 201208030301561_AddType.cs ,這個檔案主要是實際對 DB 刪減欄位時的程式碼,這會自動被呼叫。程式碼內容會是如下,定義的是 AddColumn & DropColumn 等方法:

   1:  namespace Mvc4DatabaseMigrationApp1.Migrations
   2:  {
   3:      using System;
   4:      using System.Data.Entity.Migrations;
   5:      
   6:      public partial class AddType : DbMigration
   7:      {
   8:          public override void Up()
   9:          {
  10:              AddColumn("dbo.Products", "Type", c => c.String());
  11:          }
  12:          
  13:          public override void Down()
  14:          {
  15:              DropColumn("dbo.Products", "Type");
  16:          }
  17:      }
  18:  }

 

 

最後,真的要加入欄位的話,還必須執行 Update-Database 的 Cmd-Let 命令,如下:

image

這個時候程式的執行就不會在出現錯誤了,新增加的欄位也可以編輯與使用,如下:

image

且,切換到 SQL Server Management Studio 的畫面中您也可以看見確實新增了 [Type] 這個欄位,如下:

image

前面有提到,如果想要改為自己的其他資料庫的話,可以在 web.config 加入新的 ConnectionString 即可。需要注意的是 name 必須為你的 Context 類別的名稱,此例子中為 ProductContext ,設定方式如下:

image

Ok, 以上簡單的介紹 DB Migration 的新功能。

謝謝各位。


 

簽名:

學習是一趟奇妙的旅程

這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。

軟體開發之路(FB 社團)https://www.facebook.com/groups/361804473860062/

Gelis 程式設計訓練營(粉絲團)https://www.facebook.com/gelis.dev.learning/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^