所有需要進行數據訪問的操作都須依賴Model提供的服務。簡單地說,Model負責通過數據庫、AD(Active Directory)、Web Service及其他方式取得數據,或者將用戶數據輸入的數據保存到數據庫、AD、Web Service等中。
一、Model的任務
Model的獨立性很高,所以VS方案中有多個要開發的項目,一般會將Model獨立成一個項目,好讓Model項目在不同的項目之間共享。
二、創建基礎數據模型
使用MVC開發MVC項目時,不妨好好利用VS開發工具帶來的便利。尤其是開發繁瑣的Model任務時,若使用內置化開發工具,能有效提升整體開發效率。使用Entity Framwork開發數據模型的界面,通過"模型瀏覽器"窗口,可以方便地瀏覽所有數據庫與實體對象的對應,並能通過可拖拽的可視化工具開發與定義模型之間的關系。
2.1 用LINQ to SQL自動創建數據模型
Step01:選擇mvc項目下的"Models"文件夾,單擊鼠標右鍵,在彈出的快捷菜單中一次選取"添加"—"新建項目"選項。
Step02:在"添加新項目"窗口中選擇"數據"模板,再選擇"LIBQ to SQL類"選項,輸入文件名稱,創建dbml文件。
Step03:在"視圖"下拉列表中選擇"服務器資源管理器"選項,並在"服務器資源管理器"窗口中新建數據連接。選選中"數據連接"選項,單擊鼠標右鍵,在彈出的快捷慘淡中依次選擇"數據連接"—"加入數據連接"選項。
Step04:將定義好的數據庫連接打開,並將要運用子啊MVC的數據表拖拽到DBML的設計視圖。
Step05:VS2010會自動產生所有與SQL Server數據庫對應的實體對象。
基本上已經創建完所有MVC需要的數據模型了。在創建完的后自動生成的類文件中,可以看到許多通過VS自動產生的類,這些文件內容是所有與數據庫表格對應的.NET類。
2.2 用Entity Framwork自動創建數據模型
即選中MVC項目的Models文件夾,單擊鼠標右鍵,選擇"添加"—"新建項目"選項。然后選擇"數據"模板中的ADO.NET 實體數據模型。然后按照步驟和實際表格情況選擇。(略)
如果希望Entity Framework能正確處理默認值字段,就必須手動編輯edmx文件的xml代碼,並將這些字段逐一修正。
Step01:在"解決方案..."選擇"Model1.edmx"文件並單擊鼠標右鍵,選擇打開方式,用不同的打開方式打開文件。
Step02:選擇"XML(文字)編輯器"打開。
Step03:打開后,找到SSDL程序段,並找到每一個EntityType段。
Step04:在含有默認值的字段<Property>標簽中加上"StoreGeneratePattern="Computed""
2.3 手動創建數據模型
在MVC中手動創建模型,其實跟創建一般的C#類沒有什么不同,范例如下:
1 Public class MessageViewModel 2 { 3 public int TotalPage { get; set; } 4 public int TotalPage { get; set; } 5 public IEnumerable<Message> Messages { get; set; } 6 }
三、擴充基礎數據模型
TIP :雖然通過工具產生的數據模型類別還是可以手動修改,但是通常不會去修改這些內容,否則下次再通過工具修改數據模型定義時,又要重新生成程序代碼,並覆蓋我們先前自定義的部分。
3.1 定義Model的Metadata
Metadata用於定義數據模型的相關屬性,例如顯示名稱、數據長度及數據格式驗證等。
System.ComponentModel.DataAnnotatis命名空間的類提供了驗證屬性,如圖:
屬性名稱 |
描 述 |
StringLength |
字符串字段所允許的最大長度 |
Required |
必填字段 |
RegularExpression |
字段內容必須符合所指定的規則表達式 |
Range |
數字字段必須符合的范圍 |
以下建一個簡單的會員數據模型類范例。
利用System.ComponentModel.DataAnnotatis命名空間為每個字段加上批注。每個會員都有姓名、E-mail及表情圖3個字段:姓名必填,用Required屬性;E-mail必須符合正確格式,用Regular Expression屬性驗證;表情圖需從限定的3個圖示中挑選一個,在數據庫里以int格式來進行定義。所有用Range屬性驗證只能為1~3的整數。范例代碼如下:
1 public class Member 2 { 3 [Required] 4 public string Name{get;set;} 5 [RegularExpression(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)| (([\w-]+\.)+))([a-zA-Z]{2,4})$",ErrorMessage="請輸入正確的Email格式")] 6 public string Email{get;set;} 7 [Range(1,3,ErrorMessage="請選擇代表圖示")] 8 public Int32 EmotionIcon{get;set;} 9 }
上述定義方式不適用於LINQ to SQL環境。因為在LINQ to SQL環境所有數據模型的類都由VS自動產生。不會去手動修改生成的代碼,而是通過分類的方式來延伸這個類的輔助信息。部分類代碼:
1 namespace MvcGuestbook.Models 2 { 3 public partial class Member 4 { 5 } 6 }
在部分類中直接寫上同名的屬性(Property)時,必須通過DataAnnotations命名空間提供的MetadataType屬性來克服這個限制,這樣才能在不分類中加上個字段的屬性(Attribute)。
TIP :由於只有方法、類、結構或接口可以被聲明為partial,因此不能再部分類中為現有的屬性(Property)應用額外屬性(attribute)。
這種特殊寫法可以參考以下范例,要先在不分類上應用一個MetadataType屬性,並導入一個用來設定Metadata的對象類。這個Metadata的對象類可以直接在數據模型部分類里聲明,並設定為私用類(Private Class),示例如下:
最后,完成的程序代碼如下:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.ComponentModel.DataAnnotations; 6 using System.ComponentModel; 7 8 namespace MvcApplication1.Models 9 { 10 [MetadataType(typeof(MemberMetadata))] 11 public partial class Member 12 { 13 private class MemberMetadata 14 { 15 public int ID { get; set; } 16 17 [Required(ErrorMessage = "請輸入賬號")] 18 [StringLength(50, ErrorMessage = "請勿輸入超過50個字")] 19 [DisplayName("賬號")] 20 public string Account { get; set; } 21 22 [Required(ErrorMessage = "請輸入密碼")] 23 [StringLength(50, ErrorMessage = "請勿輸入超過50個字")] 24 [DisplayName("密碼")] 25 public string Password { get; set; } 26 27 [Required(ErrorMessage = "請輸入昵稱")] 28 [StringLength(50, ErrorMessage = "請勿輸入超過50個字")] 29 [DisplayName("昵稱")] 30 public string NickName { get; set; } 31 32 [Required(ErrorMessage = "請輸入中文名")] 33 [StringLength(50, ErrorMessage = "請勿輸入超過50個字")] 34 [DisplayName("中文姓名")] 35 public string CHName { get; set; } 36 37 [Required(ErrorMessage = "請輸入Email")] 38 [StringLength(255, ErrorMessage = "請勿輸入超過255個字")] 39 [DisplayName("Email")] 40 [RegularExpression(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)| (([\w-]+\.)+))([a-zA-Z]{2,4})$", ErrorMessage = "請輸入正確的Email格式")] 41 public string Email { get; set; } 42 43 public bool IsAdmin { get; set; } 44 45 [Required(ErrorMessage = "請選擇代表圖標")] 46 [Range(1, 3, ErrorMessage = "輸入的值必須介於1到3之間")] 47 [DisplayName("代表圖標")] 48 public int EmotionIcon { get; set; } 49 50 public DateTime CreateTime { get; set; } 51 } 52 } 53 }
NOTE :采用上述方法只是為了使用MetadataType屬性來擴充個字段的屬性(Attribute),而對於這些MetadataType屬性中所定義的屬性(Property),其所定義的類並不重要,重要的是這些屬性(Property)名稱要與數據模型類中定義的屬性(Property)名稱一樣—就算你將所有字段都定義成object類也沒關系。
3.2 自定義Metadata屬性
前面已經用RegularExpression
屬性來驗證E-mail字段,但如果有大量使用需要,程序代碼就會顯得有些復雜,可以視需要來自定義驗證屬性。以驗證E-mail屬性。以E-mail字段為例,可以繼承RegularExpressionAttribute類,並實現另一個驗證屬性,示例如下:
1 public class EmailAttribute : RegularExpressionAttribute 2 { 3 public EmailAttribute() : 4 base(@"^([\w-\.]+)@((\[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.))([a-zA-Z]{2,4})$") { } 5 }
如此一來,就可以使用Email屬性來聲明字段的驗證規則了,示例如下:
1 [Email(ErrorMessage = "請輸入正確的Email.")] 2 public string Email { get; set; }
四、實現庫模式
庫模式(Repository pattern)是專門用於訪問數據的一種樣式(Pattern),其設計方式很簡繁:首先定義接口(Interface),看你希望將什么樣的接口提供給訪問數據庫的類(如Controller),接着再實現該接口。接口與類(Class)的切割將有助於開發單元測試,也可讓測試驅動開發(Test Driven Development,TDD)進行得較為順利。以會員數據為例來說明如何實現庫樣式,主要有3個步驟。
Step01:創建接口,定義可操作的方法。
Step02:創建類,實現接口。
Step03:在Controller中以接口來操作這個類。
范例:會員數據
Step01:創建ImemberRepository接口,並將其作為訪問會員數據接口,示例如下:
1 public interface IMemberRepository 2 { 3 IQueryable<Member> FindAllMembers(); 4 Member GetMemberById(int id); 5 Member GetMemberByAccount(string account); 6 void Add(Member Member); 7 bool Delete(int id); 8 void Save(); 9 }
Step02:實現IMemberRepository接口,示例如下。
1 public class MemberRepository : IMemberRepository 2 { 3 protected MvcApplication1.Models.GuestbookEntities db = new Models.GuestbookEntities(); 4 //protected MvcGuestBookDataContext db = new MvcGuestBookDataContext(); 5 IQueryable<Member> IMemberRepository.FindAllMembers() 6 { 7 return db.Member; 8 } 9 Member IMemberRepository.GetMemberById(int id) 10 { 11 return db.Member.Where(p => p.ID == id).FirstOrDefault(); 12 } 13 Member IMemberRepository.GetMemberByAccount(string account) 14 { 15 return db.Member.Where(p => p.Account == account).FirstOrDefault(); 16 } 17 void IMemberRepository.Add(Member Member) 18 { 19 db.Member.InsertOnSubmit(Member); 20 } 21 bool IMemberRepository.Delete(int id) 22 { 23 var m = db.Member.FirstOrDefault(p => p.ID == id); 24 if (m != null) 25 { 26 db.Member.DeleteOnSubmit(m); 27 return true; 28 } 29 else 30 { 31 return false; 32 } 33 } 34 void IMemberRepository.Save() 35 { 36 db.SubmitChanges(); 37 } 38 }
Step03 :在Controller訪問數據時,可定義一個IMemberRepository接口類的對象,讓該Controller能使用這些對象來訪問數據,並新建Controller類的構造符,讓該Controller類可以先創建MemberRepository類的實體,程序范例如下。
1 public class MemberController : Controller 2 { 3 IMemberRepository _r; 4 public MemberController() 5 : this(new MemberRepository()) 6 { } 7 public MemberController(IMemberRepository r) 8 { 9 _r = r; 10 } 11 }