所有需要进行数据访问的操作都须依赖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 }