# [.NET領域驅動設計實戰系列]專題一:前期準備之EF CodeFirst
## 一、前言
從去年已經接觸領域驅動設計(Domain-Driven Design)了,當時就想自己搭建一個DDD框架,所以當時看了很多DDD方面的書,例如領域驅動模式與實戰,領域驅動設計:軟件核心復雜性應對之道和領域驅動設計C# 2008實現等書,由于當時只是看看而已,并沒有在自己代碼中進行實現,只是初步了解一些DDD分層的思想和一些基本概念,例如實體,聚合根、倉儲等概念,今年有機會可以去試試面試一個架構崗位的時候,深受打擊,當面試官問起是否在項目中使用過DDD思想來架構項目時,我說沒有,只是了解它的一些基本概念。回來之后,便重新開始學習DDD,因為我發現做成功面試一個架構師,則必須有自己的一個框架來代表自己的知識體系,而不是要你明白這個基本概念。此時學習便決定一步步來搭建一個DDD框架。但是這次的過程并不是像dax.net那樣,一開始就去搭建框架,然后再用一個實際的項目來做演示框架的使用。因為我覺得這樣對于一些初學者來學習的話,難度比較大,因為剛開始寫框架根本看到什么,而且看dax.net的Apworks框架很多代碼也不明白他為什么這么寫的,從框架代碼并不能看出作者怎么一步步搭建框架的,讀者只能一下子看到整個成型的框架,對于剛接觸DDD的朋友難度非常大,以至于學習了一段時間的DDD之后,就放棄了。這個感覺本人學習過程中深有體會。所以本系列將直接把DDD的思想應用到一個實例項目中,完全實例項目后,再從中抽取一個DDD框架出來,并且會一步步介紹如何將DDD的思想應用到一個實際項目中(dax.net中ByteartRetail項目也是直接給出一個完整的DDD演示項目的,并沒有記錄搭建過程,同樣對于讀者學習難度很大,因為一下子來吸收整個項目的知識,接受不了,讀者自然就心灰意冷,也就沒有繼續學習DDD的動力了)。本文并沒有開始介紹DDD項目的實際實現,而是一個前期準備工作,因為DDD項目中一般會使用的實體框架來完成,作為.NET陣營的人,自然首先會使EntityFramework。下面就具體介紹下EF中code First的實現,因為在后面的DDD項目實現中會使用到EF的CodeFirst。
## 二、EF CodeFirst的實現步驟
因為我之前沒怎么接觸EF的CodeFirst實現,所以在看dax.net的ByteartRetail項目的時候,對EF倉儲的實現有疑惑,所以去查閱相關EF的教程發現,原來應用了EF中的CodeFirst。所以把過程記錄下來。下面就具體介紹下使用EF CodeFirst的具體實現步驟。
**步驟一:創建一個Asp.net MVC 4 Web項目,創建成功后,再添加一個Model類**
CodeFirst自然是先寫實體類了,這里演示的是一個Book實體類,具體類的實現代碼如下:
```
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
public string Author { get; set; }
public string Publisher { get; set; }
public decimal Price { get; set; }
public string Remark { get; set; }
}
```
將使用這個類表示數據庫中的一個表,每個Book類的實例對應數據庫中的一行,Book類中的每個屬性映射為數據庫中的一列。
**步驟二:創建“BookDbContext”的類**
使用Nuget安裝Entity Framework,安裝成功后,在Models文件夾下新建一個“BookDbContext”的類,將類派生自“DbContext”類(命名空間為System.Data.Entity,dll在EntityFramework),具體BookDbContext的實現如下:
BookDbContext代表EF中Book在數據庫中的上下文對象,通過DbSet<Book>使實體類與數據庫關聯起來。Books屬性表示數據中的數據集實體,用來處理數據的存取與更新。
**步驟三:添加數據庫連接**
在Web.config文件中,修改數據庫連接字符串的配置,這里將數據庫連接的name屬性設置為BookDbContext,后面代碼將會使用到該名字,并根據連接創建相應的數據庫。
**步驟四:為Book創建控制器和Index視圖**
首先創建一個控制器:在"Controlllers"上右鍵>添加>控制器,在打開的添加控制器對話框中,將控制器的名稱改為"BookController",模板選擇”空控制器“。修改BookController的代碼為如下所示:
```
public class BookController : Controller
{
readonly BookDbContext _db = new BookDbContext();
//
// GET: /Book/
public ActionResult Index()
{
var books = from b in _db.Books
where b.Author == "Learninghard"
select b;
return View(books.ToList());
}
public ActionResult Create()
{
return View();
}
}
```
在Index方法內右鍵>"添加視圖",在打開的”添加視圖“對話框,勾選”創建強類型視圖“,在模型類列表中選擇”Book“(如果選擇列表為空,則需要首先編譯下項目),在支架模板列表中選擇”List“,具體如下圖所示:

點擊添加按鈕,VS為我們創建了Index.cshtml文件,修改Index.cshtml代碼為如下所示的代碼:
```
@model IEnumerable<EF_CodeFirstImp.Models.Book>
@{
ViewBag.Title = "圖書列表-EF CodeFirstImp";
}
<h2>Index</h2>
<p>
@Html.ActionLink("添加圖書", "Create")
</p>
<table>
<tr>
<th>
圖書名稱
</th>
<th>
作者
</th>
<th>
出版社
</th>
<th>
價格
</th>
<th>
備注
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.BookName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Author)
</td>
<td>
@Html.DisplayFor(modelItem => item.Publisher)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Remark)
</td>
<td>
@Html.ActionLink("編輯", "Edit", new { id=item.BookId }) |
@Html.ActionLink("查看", "Details", new { id=item.BookId }) |
@Html.ActionLink("刪除", "Delete", new { id=item.BookId })
</td>
</tr>
}
</table>
```
編譯并運行程序,在瀏覽器中輸入地址:http://localhost:2574/Book,得到的運行結果如下:

盡管沒有數據,但EF已經為我們創建了相應的數據庫了。此時在App_Data文件夾下生成了BookDB數據庫,在解決方案點擊選擇所有文件,將BookDB數據庫包括在項目中。
**步驟五:添加Create視圖**
在BookController中的Create方法右鍵添加視圖來添加Create視圖,此時模型類仍然選擇Book,但支架模板選擇"Create"。添加成功后,VS會在Views/Book目錄下添加一個Create.cshtml文件,由于這里選擇了Create支架框架,所以VS會為我們生成一些默認的代碼。在這個視圖模板中,指定了強類型Book作為它的模型類,VS檢查Book類,并根據Book類的屬性,生成對應的標簽名和編輯框,我們修改標簽使其顯示中文,修改會的代碼如下所示:
```
@model EF_CodeFirstImp.Models.Book
@{
ViewBag.Title = "添加圖書";
}
<h2>添加圖書</h2>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
<legend>圖書</legend>
<div class="editor-label">
圖書名稱:
</div>
<div class="editor-field">
@Html.EditorFor(model => model.BookName)
@Html.ValidationMessageFor(model => model.BookName)
</div>
<div class="editor-label">
作者:
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Author)
@Html.ValidationMessageFor(model => model.Author)
</div>
<div class="editor-label">
出版社:
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Publisher)
@Html.ValidationMessageFor(model => model.Publisher)
</div>
<div class="editor-label">
價格:
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<div class="editor-label">
備注:
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Remark)
@Html.ValidationMessageFor(model => model.Remark)
</div>
<p>
<input type="submit" value="添加" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
```
分析上面的代碼:
* @model EF_CodeFirstImp.Models.Book:指定該視圖模板中的“模型”強類型化是一個Book類。
* @using (Html.BeginForm()){ }:創建一個Form表單,在表單中包含了對于Book類所生成的對應字段。
* @Html.EditorFor(model => model.BookName):根據模型生成模型中BookName的編輯控件(生成一個Input元素)
* @Html.ValidationMessageFor(model => model.BookName):根據模型生成模型中BookName的驗證信息。

**步驟六:添加Create的Postback方法**
```
public class BookController : Controller
{
readonly BookDbContext _db = new BookDbContext();
//
// GET: /Book/
public ActionResult Index()
{
var books = from b in _db.Books
where b.Author == "Learninghard"
select b;
return View(books.ToList());
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Book book)
{
if (ModelState.IsValid)
{
_db.Books.Add(book);
_db.SaveChanges();
return RedirectToAction("Index");
}
else
return View(book);
}
}
```
這時,我們在添加圖書界面中輸入數據,并點擊”添加“按鈕時,數據庫中就會添加一行記錄。打開數據庫,我們將看到如下截圖的數據:

** 步驟七:設置視圖模型的數據驗證**
我們可以在模型類中顯式地追加一個驗證規則,使得對輸入數據進行驗證。修改之前的Book類為如下:
```
using System.ComponentModel.DataAnnotations; //需要額外添加該命名空間
public class Book
{
public int BookId { get; set; }
[Required(ErrorMessage = "必須輸入圖書名稱")]
[StringLength(maximumLength: 100, MinimumLength = 1, ErrorMessage = "最多允許輸入100個字符")]
public string BookName { get; set; }
[Required(ErrorMessage ="必須輸入作者名稱")]
public string Author { get; set; }
[Required(ErrorMessage ="必須輸入出版社名稱" )]
public string Publisher { get; set; }
public decimal Price { get; set; }
public string Remark { get; set; }
}
```
此時重新運行,并打開添加圖書頁面,當不輸入任何數據的時候,點擊”添加“按鈕時,界面會出現一些提示信息,并阻止我們進行數據的提交操作,具體的結果界面如下所示:

另外,EF創建數據庫除了在第三步中添加連接字符串的方式外,還可以定義defaultConnectionFactory中添加parameters節點的方式來完成,dax.net中ByteartRetail項目中就是采用了這種方式。下面注釋掉connectionStrings節點,在defaultConnectionFactory添加如下parameters節點:
```
<entityFramework>
**<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" >
<parameters>
<parameter value="Data Source=(LocalDb)\v11.0;Initial Catalog=BookDB_2;Integrated Security=True;AttachDBFilename=|DataDirectory|\BookDB_2.mdf"/>
</parameters>
</defaultConnectionFactory>**
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
```
entityFramework節點是使用Nuget添加Entity Framework后自動添加的節點。下面測試下這種方案是否可以成功生成**BookDB_2**數據庫呢?
運行項目,在瀏覽器中輸入地址:http://localhost:2574/Book,顯示界面成功后,你將在你的App_Data目錄下看到如下截圖:

從上圖可以發現,這種方式同樣成功生成了數據庫。
## 三、總結
到這里,領域驅動設計實戰系列的前期準備就結束了,本文主要介紹了如何使用EF CodeFirst的功能自動生成數據庫、以及實體的添加、查看操作等。這里也簡單介紹了MVC相關內容。下一專題將介紹如何利用DDD的思想來構建一個簡單的網站,接下來的系列就逐一加入DDD的概念來對該網站進行完善。
本文所有源碼下載:[EFCodeFirstImp.zip](http://files.cnblogs.com/files/zhili/EFCodeFirstImp.zip)
- C# 基礎知識系列
- C# 基礎知識系列 專題一:深入解析委托——C#中為什么要引入委托
- C# 基礎知識系列 專題二:委托的本質論
- C# 基礎知識系列 專題三:如何用委托包裝多個方法——委托鏈
- C# 基礎知識系列 專題四:事件揭秘
- C# 基礎知識系列 專題五:當點擊按鈕時觸發Click事件背后發生的事情
- C# 基礎知識系列 專題六:泛型基礎篇——為什么引入泛型
- C# 基礎知識系列 專題七: 泛型深入理解(一)
- C# 基礎知識系列 專題八: 深入理解泛型(二)
- C# 基礎知識系列 專題九: 深入理解泛型可變性
- C#基礎知識系列 專題十:全面解析可空類型
- C# 基礎知識系列 專題十一:匿名方法解析
- C#基礎知識系列 專題十二:迭代器
- C#基礎知識 專題十三:全面解析對象集合初始化器、匿名類型和隱式類型
- C# 基礎知識系列 專題十四:深入理解Lambda表達式
- C# 基礎知識系列 專題十五:全面解析擴展方法
- C# 基礎知識系列 專題十六:Linq介紹
- C#基礎知識系列 專題十七:深入理解動態類型
- 你必須知道的異步編程 C# 5.0 新特性——Async和Await使異步編程更簡單
- 全面解析C#中參數傳遞
- C#基礎知識系列 全面解析C#中靜態與非靜態
- C# 基礎知識系列 C#中易混淆的知識點
- C#進階系列
- C#進階系列 專題一:深入解析深拷貝和淺拷貝
- C#進階系列 專題二:你知道Dictionary查找速度為什么快嗎?
- C# 開發技巧系列
- C# 開發技巧系列 使用C#操作Word和Excel程序
- C# 開發技巧系列 使用C#操作幻燈片
- C# 開發技巧系列 如何動態設置屏幕分辨率
- C# 開發技巧系列 C#如何實現圖片查看器
- C# 開發技巧 如何防止程序多次運行
- C# 開發技巧 實現屬于自己的截圖工具
- C# 開發技巧 如何使不符合要求的元素等于離它最近的一個元素
- C# 線程處理系列
- C# 線程處理系列 專題一:線程基礎
- C# 線程處理系列 專題二:線程池中的工作者線程
- C# 線程處理系列 專題三:線程池中的I/O線程
- C# 線程處理系列 專題四:線程同步
- C# 線程處理系列 專題五:線程同步——事件構造
- C# 線程處理系列 專題六:線程同步——信號量和互斥體
- C# 多線程處理系列專題七——對多線程的補充
- C#網絡編程系列
- C# 網絡編程系列 專題一:網絡協議簡介
- C# 網絡編程系列 專題二:HTTP協議詳解
- C# 網絡編程系列 專題三:自定義Web服務器
- C# 網絡編程系列 專題四:自定義Web瀏覽器
- C# 網絡編程系列 專題五:TCP編程
- C# 網絡編程系列 專題六:UDP編程
- C# 網絡編程系列 專題七:UDP編程補充——UDP廣播程序的實現
- C# 網絡編程系列 專題八:P2P編程
- C# 網絡編程系列 專題九:實現類似QQ的即時通信程序
- C# 網絡編程系列 專題十:實現簡單的郵件收發器
- C# 網絡編程系列 專題十一:實現一個基于FTP協議的程序——文件上傳下載器
- C# 網絡編程系列 專題十二:實現一個簡單的FTP服務器
- C# 互操作性入門系列
- C# 互操作性入門系列(一):C#中互操作性介紹
- C# 互操作性入門系列(二):使用平臺調用調用Win32 函數
- C# 互操作性入門系列(三):平臺調用中的數據封送處理
- C# 互操作性入門系列(四):在C# 中調用COM組件
- CLR
- 談談: String 和StringBuilder區別和選擇
- 談談:程序集加載和反射
- 利用反射獲得委托和事件以及創建委托實例和添加事件處理程序
- 談談:.Net中的序列化和反序列化
- C#設計模式
- UML類圖符號 各種關系說明以及舉例
- C#設計模式(1)——單例模式
- C#設計模式(2)——簡單工廠模式
- C#設計模式(3)——工廠方法模式
- C#設計模式(4)——抽象工廠模式
- C#設計模式(5)——建造者模式(Builder Pattern)
- C#設計模式(6)——原型模式(Prototype Pattern)
- C#設計模式(7)——適配器模式(Adapter Pattern)
- C#設計模式(8)——橋接模式(Bridge Pattern)
- C#設計模式(9)——裝飾者模式(Decorator Pattern)
- C#設計模式(10)——組合模式(Composite Pattern)
- C#設計模式(11)——外觀模式(Facade Pattern)
- C#設計模式(12)——享元模式(Flyweight Pattern)
- C#設計模式(13)——代理模式(Proxy Pattern)
- C#設計模式(14)——模板方法模式(Template Method)
- C#設計模式(15)——命令模式(Command Pattern)
- C#設計模式(16)——迭代器模式(Iterator Pattern)
- C#設計模式(17)——觀察者模式(Observer Pattern)
- C#設計模式(18)——中介者模式(Mediator Pattern)
- C#設計模式(19)——狀態者模式(State Pattern)
- C#設計模式(20)——策略者模式(Stragety Pattern)
- C#設計模式(21)——責任鏈模式
- C#設計模式(22)——訪問者模式(Vistor Pattern)
- C#設計模式(23)——備忘錄模式(Memento Pattern)
- C#設計模式總結
- WPF快速入門系列
- WPF快速入門系列(1)——WPF布局概覽
- WPF快速入門系列(2)——深入解析依賴屬性
- WPF快速入門系列(3)——深入解析WPF事件機制
- WPF快速入門系列(4)——深入解析WPF綁定
- WPF快速入門系列(5)——深入解析WPF命令
- WPF快速入門系列(6)——WPF資源和樣式
- WPF快速入門系列(7)——深入解析WPF模板
- WPF快速入門系列(8)——MVVM快速入門
- WPF快速入門系列(9)——WPF任務管理工具實現
- ASP.NET 開發
- ASP.NET 開發必備知識點(1):如何讓Asp.net網站運行在自定義的Web服務器上
- ASP.NET 開發必備知識點(2):那些年追過的ASP.NET權限管理
- ASP.NET中實現回調
- 跟我一起學WCF
- 跟我一起學WCF(1)——MSMQ消息隊列
- 跟我一起學WCF(2)——利用.NET Remoting技術開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(4)——第一個WCF程序
- 跟我一起學WCF(5)——深入解析服務契約 上篇
- 跟我一起學WCF(6)——深入解析服務契約 下篇
- 跟我一起學WCF(7)——WCF數據契約與序列化詳解
- 跟我一起學WCF(8)——WCF中Session、實例管理詳解
- 跟我一起學WCF(9)——WCF回調操作的實現
- 跟我一起學WCF(10)——WCF中事務處理
- 跟我一起學WCF(11)——WCF中隊列服務詳解
- 跟我一起學WCF(12)——WCF中Rest服務入門
- 跟我一起學WCF(13)——WCF系列總結
- .NET領域驅動設計實戰系列
- .NET領域驅動設計實戰系列 專題一:前期準備之EF CodeFirst
- .NET領域驅動設計實戰系列 專題二:結合領域驅動設計的面向服務架構來搭建網上書店
- .NET領域驅動設計實戰系列 專題三:前期準備之規約模式(Specification Pattern)
- .NET領域驅動設計實戰系列 專題四:前期準備之工作單元模式(Unit Of Work)
- .NET領域驅動設計實戰系列 專題五:網上書店規約模式、工作單元模式的引入以及購物車的實現
- .NET領域驅動設計實戰系列 專題六:DDD實踐案例:網上書店訂單功能的實現
- .NET領域驅動設計實戰系列 專題七:DDD實踐案例:引入事件驅動與中間件機制來實現后臺管理功能
- .NET領域驅動設計實戰系列 專題八:DDD案例:網上書店分布式消息隊列和分布式緩存的實現
- .NET領域驅動設計實戰系列 專題九:DDD案例:網上書店AOP和站點地圖的實現
- .NET領域驅動設計實戰系列 專題十:DDD擴展內容:全面剖析CQRS模式實現
- .NET領域驅動設計實戰系列 專題十一:.NET 領域驅動設計實戰系列總結