所有需要进行数据访问的操作都须依赖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),示例如下:
最后,完成的程序代码如下:
View Code
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 }