昨天我們已經建立了一個簡單的Api,提供使用者可以透過簡單的Get方法來取得JSon格式的資料,但在真實世界的應用中,我們還是最常透過Api來存取在資料庫中的資料,今天的文章就是要實際的將Api串接到資料庫中,並且透過好用的Entity Framework,讓我們可以先撰寫程式碼,再來透程式碼來產生DB的Schema。
在Entity Framework 5.0中,更支援了好用的Migration功能,讓每一次資料庫調整都可以透過Entity Framework自動產生升版和降版的程式碼,未來要升級或Rollback資料庫都會變得更加的方便囉!
大家可以從Github - ApiSample Day 04取得之前的程式碼,開始練習今天的範例
※建立Api專案使用的LocalDB環境
LocalDB是微軟最新推出的一種適合在開發環境使用的資料庫,相較於Sql Server Express,它安裝和建立新個體的速度非常的快,再搭配上Entity Framework的Seed功能,要快速的安裝出一個開發或測試環境是非常簡單的事情,接下來就讓我們建立給這個專案使用的Local DB吧!
註: 請先安裝LocalDB
打開命令提示字元,輸入指令建立給專案使用的Localdb
SqlLocalDB create ApiSample
2. 我們可以透過Sql Management Studio連線到LocalDB,確認資料庫成功的建立
(localdb)\ApiSample
3. 成功看到資料庫已被建立
延伸閱讀:
* SQL Server 2012 Express LocalDB (SqlLocalDB) 深入剖析
※建立Entity Framework Code First專案
假設我們今天所要建立的Api是一個網路商店所使用的Api,在初期預計會提供商品以及商店分類這兩種Table,我們就以這個假設來設計我們的資料庫。
首先規劃出我們的資料結構
在DA建立Tables專案,專門用來放資料庫對應的實體Class,也就是Code First的POCO Entity
建立ShopContext.cs,並包含Category和Product的集合
public class ShopContext : DbContext
{
public IDbSet Categories { get; set; }
public IDbSet<Product> Products { get; set; }
}
建立Product.cs,包含預先設計的欄位
public class Product
{
[Key]
public int Id { get; set; }
[StringLength(100)]
[Required]
public string Name { get; set; }
public decimal Price { get; set; }
public decimal Cost { get; set; }
[StringLength(1000)]
public string Description { get; set; }
public DateTime SellingStartTime { get; set; }
public DateTime SellingEndTime { get; set; }
public Category Category { get; set; }
}
建立Category.cs,並建立和Product之間的關聯
public class Category
{
[Key]
public int Id { get; set; }
public int? ParentId { get; set; }
[StringLength(100)]
[Required]
public string Name { get; set; }
[ForeignKey("ParentId")]
public ICollection<Category> Categories { get; set; }
public ICollection<Product> Products { get; set; }
}
將WebSite設成啟動專案,並修改Web.Config檔,加入對應ShopContext的ConnectionString
以下是關於ConnectionString的說明,EntityFramework預設會自動偵測是否有建立資料庫,若沒有的話會自動根據之前所定義的結構長出資料庫,而資料庫的生成路徑、名稱和在LocalDB的哪一個專案都是在設定檔中決定,也可透過設定檔關閉偵測資料庫若不存在則自動生成資料庫的預設行為。
name - 必須要跟ShopContext的Namespace+class name一樣才讀取的到設定,或是在ShopContext的Constructor指定
connectionString的Catalog - 代表資料庫的名稱
connectionString的AttachDBFilename - 資料庫實體的位置
<connectionStrings>
<add name="ApiSample.DA.Tables.ShopContext"
providerName="System.Data.SqlClient"
connectionString="Data Source=(LocalDb)\ApiSample;Initial Catalog=ShopContextDB;Integrated Security=SSPI;AttachDBFilename=C:\LocalDB\ApiSample\ShopContext.mdf" />
</connectionStrings>
打開套件管理主控台(Package Management Console),預設專案選擇Tables,新增一個Migration
Add-Migration CreateDatabase
7. 我們可以看到Migration檔案中同時包含了升版和降版的程式碼,每次修改資料庫結構,我們都會建立一個包含升版和降版語法的Migration,因此我們日後可以很輕鬆的指定要將資料庫升級或是回復到哪一個Migration版本
8. 在WebSite專案加入Tables專案參考,在套件管理主控台輸入更新資料庫指令
Update-Database
9. 我們可以從Sql Management Studio看到Table已被正確建立
延伸閱讀:
* Code First Data Annotations
* Code First Migrations
※將資料庫資料提供給Api查詢
在建立完資料庫之後,我們接下來要處理Api呈現資料的部分,透過Entity Framework來查詢並提供資料給畫面呈現
建立Repository,提供DA層的介面以及實體,提供查詢Category下的Product,並只顯示在上架時間之內的商品
IProductRepository.cs
public interface IProductRepository
{
IEnumerable GetProductByCategoryId(int categoryId);
}
ProductRepository.cs
public class ProductRepository : IProductRepository
{
public ShopContext ShopContext { get; set; }
public ProductRepository(ShopContext context)
{
this.ShopContext = context;
}
public IEnumerable<ProductForCategoryModel> GetProductByCategoryId(int categoryId)
{
var result = this.ShopContext.Products.Where(i => i.ListingStartTime < DateTime.Now &&
i.ListingEndTime >= DateTime.Now &&
i.Category.Id == categoryId)
.Select(i => new ProductForCategoryModel()
{
Id = i.Id,
Name = i.Name,
Price = i.Price
});
return result;
}
}
修改RepositoryModule的設定,設定預設註冊DA所有的Type,也設定ShopContext對應的實體為自己
public class RepositoryModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
var repository = Assembly.Load("ApiSample.DA.Repositories");
builder.RegisterAssemblyTypes(repository).AsImplementedInterfaces();
builder.RegisterType<ShopContext>().As<ShopContext>();
}
}
設定BL,提供商業邏輯的處理 (本次範例沒特別的商業邏輯)
IProductService.cs
public interface IProductService
{
IEnumerable GetProductByCategoryId(int categoryId);
}
ProductService.cs
public class ProductService : IProductService
{
public IProductRepository ProductRepository { get; set; }
public ProductService(IProductRepository productRepository)
{
this.ProductRepository = productRepository;
}
public IEnumerable<ProductForCategoryModel> GetProductByCategoryId(int categoryId)
{
return this.ProductRepository.GetProductByCategoryId(categoryId);
}
}
設定BL的Module
public class ServiceModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
var service = Assembly.Load("ApiSample.BL.Services");
builder.RegisterAssemblyTypes(service).AsImplementedInterfaces();
}
}
設定Controller
public class ProductController : Controller
{
public IProductService ProductService { get; set; }
public ProductController(IProductService productService)
{
this.ProductService = productService;
}
public ActionResult GetProductByCategory(int id)
{
var result = this.ProductService.GetProductByCategoryId(id);
return Json(result, JsonRequestBehavior.AllowGet);
}
}
塞入一些假資料到資料庫,並嘗試丟入Category ID,使用get方法瀏覽Api
※本日小結
透過Entity Framework的Code First,讓我們甚至可以先決定要在程式中使用的物件,再反向來產生資料庫,甚至一句SQL都不用寫就可以完成資料庫的建立,而透過Migration讓我們資料庫結構的異動變得一目了然,要讓資料庫回復到任何一個版本也都似乎沒那麼困難了。而且Entity Framework搭配上LocalDB,我們可以快速的產生一個新的資料庫環境來作開發或測試,這些都是在以往比較難以想像的地方,關於今天的內容歡迎大家一起討論 ^_^
延伸閱讀:
* Get Started with Entity Framework (EF)