
2.5 ModelFirst开发方式
在项目一开始,没有数据库时,可以借助EF设计模型,然后根据模型同步完成数据库中表的创建,这就是Model First开发方式。总结一点就是先有模型再有表。
2.5.1创建Model First Demo
创建Model First的操作步骤如下:
(1)创建控制台项目
右击解决方案“EFDemo”,选择“添加项目→控制台应用程序”,并将项目命名为ModelFirst。
(2)添加ADO.NET实体模型
右击ModelFirst项目,再选择“添加新建项→ADO.NET实体数据模型”并命名为ModelFirstModel.edmx,创建空模型后单击“完成”。
(3)添加实体Customer
① 添加实体和几个必要的测试字段。切记,一定要添加主键。主键既可以是自增长的数字类型,也可以是Guid类型,如图2-19、图2-20所示。

图2-19

图2-20
② 添加标量属性。标量属性可以看成数据库中的普通字段(主键和外键之外的),我们在设计字段属性的时候,一定要记得设置其最大范围,否则最终会生成一个比较大的默认范围,严重影响性能,并占用不必要的磁盘空间,如图2-21、图2-22所示。

图2-21

图2-22
再依次添加标量属性Telphone、CompanyName、Age,如表2-2所示。
表2-2 Customer实体

接着看一下ID属性,ID是实体键,存储方式是自增长,如图2-23所示。

图2-23
这里简单说明一下模型属性的类型。这里的类型都是CTS中的类型,即IL中使用的类型。这些类型可以是如下选项:
● Int32。
● String,可以选择是否采用Unicode编码,如果采用就对应SQL Server中的nvarchar类型。
● Decimal,表示指定小数位数及数据精度的类型,范围表示小数个数,精度显示总的数据位数。
● 属性“可以为Null”。
● 属性“实体键”,表示设置主键。
(4)添加实体之间的联系
再添加一个实体Product,右击空白处→新增→实体,属性类型设为Guid,这时字段ID属性的存储方式为None,实体键为True,如图2-24所示。

图2-24
这里涉及自增长主键和Guid主键。
Guid比自增长要快,因为自增长会先查询表中最大的ID,然后锁表,再在这个最大的ID基础上加1,然后插入数据表。
自增长也有自己的长处,比如占用空间小,因为它是int类型的,而Guid一般是32或64个字符长度,还有就是比较清晰,不像Guid那么大。如果涉及数据迁移,自增长主键就会变得非常不方便,而Guid的优势则非常明显。
再依次添加标量属性Name、Price、Weight,如表2-3所示。
表2-3 Product实体

再添加实体Order以及标量属性OrderNO、Amount、CreateTime,如表2-4所示。
表2-4 Order实体

接着,我们添加两个实体之间的关联,右击工作面板空白处→新增→关联,在“添加关联”对话框(见图2-25)中进行设置。

图2-25
Customer和Order是一个一对多的关系,Customer实体和Order实体上面的导航属性不要去掉,因为后面用它来查询将会变得非常方便。导航属性,顾名思义,就是根据这个属性可以找到一个和它关联的对象实体。我们再添加Customer和Product多对多的关联,最终结果展示如图2-26所示。

图2-26
说明:这里只是为了演示,在实际应用中商品是和订单明细关联的。
对于“关联”的说明如下:
● 1:1,性能低(不会延迟加载,添加时必须同时创建两个对象),尽量不要使用,可以自己实现逻辑代码完成这种操作。查看一下表结构,可以发现本质还是1:m的结构。
● 1:m。
● M:n:可以手动创建中间表采用1:m关系,也可以直接使用此种关系,EF会自动创建中间表。
● 在创建关联时,可以选择是否要创建导航属性、外键。
对于“导航属性”的说明如下:
● 根据关系的不同,查看生成的导航属性的类型。
● 示例:在1对多关系中,对于多端表数据的插入。
添加完成之后,按Ctrl+S组合键,会在ModelFirstModel.edmx中ModelFirstModel.tt下面生成Customer、Order和Product三个类,如图2-27所示。.tt后缀的文件为T4模板,后面会单独讲解。
(5)根据模型创建数据库
为了方便,这里直接使用VS2012中自带的localDB,右击工作区空白处,根据模型生成数据库,将数据库命名为ModelFirstDB,并执行SQL脚本创建数据库。
如果这里是接SQL Server 2008上面创建的数据库,就需要从https://msdn.microsoft.com/enus/jj650015下载SQL Server Data Tools in Visual Studio 2012进行安装,否则后面无法直接执行SQL,因为VS2012默认是和SQL Server 2012搭配的,如图2-28所示。

图2-27

图2-28
单击“根据模型生成数据库”操作后会出现如图2-29所示的界面。

图2-29
单击“确定”按钮,出现如图2-30所示的界面。

图2-30
单击“下一步→完成”,生成DLL脚本,然后执行生成的DLL脚本,如图2-31所示。

图2-31
单击“执行”按钮后,出现如图2-32所示的界面。

图2-32
执行DLL脚本完成之后,我们会看到数据库中出现了4张表,如图2-33所示。

图2-33
CustomerProduct表是怎么回事呢?因为我们之前添加了多对多关联,而多对多关联就是通过一张新表来实现存储的。
(6)导航属性的应用
①创建测试数据。
记住,要先添加Customer实体的数据,因为Order实体和Product实体都引用了Customer实体。
在Program类中执行如下方法:
static void AddTestData() { using (ModelFirstModelContainer db = new ModelFirstModelContainer()) { Customer _Customer = new Customer { Name="楚留香 ", Age=27, CompanyName="大旗门", Telphone="15243641131"}; Order _Order1 = new Order {Amount=15, CreateTime=DateTime.Now, OrderNO="2016043001", CustomerID=_Customer.ID}; Order _Order2 = new Order { Amount = 16, CreateTime = DateTime.Now, OrderNO = "2016043002", Customer = _Customer }; Product _Product = new Product {ID=Guid.NewGuid(), Name="牛栏1段 ", Price=12, Weight=80, Customer=new List<Customer>(){_Customer}}; db.Customer.Add(_Customer); db.Product.Add(_Product); db.Order.Add(_Order1); db.Order.Add(_Order2); if (db.SaveChanges() > 0) { Console.WriteLine("添加成功!"); } else { Console.WriteLine("添加失败!"); } } }
注意:db.SaveChanges()默认是已经开启了事务的,而且在这之前都只进行了一次数据库的连接,这种类似于批处理的操作大大地提升了性能。
② 查询客户楚留香的所有订单信息:
static void SearchCusOrder() { using (ModelFirstModelContainer db = new ModelFirstModelContainer()) { var _orderList = from o in db.Order where o.Customer.Name == "楚留香" select o; Console.WriteLine("客户楚留香的所有订单如下:"); _orderList.ToList().ForEach(o=>Console.WriteLine(string.Format("订单 号:{0},订单金额:{1},订单创建时间:{2}", o.OrderNO, o.Amount, o.CreateTime))); Console.ReadKey(); } }
③ 运行结果如图2-34所示。

图2-34
这里先查Order表信息,然后直接通过导航属性Customer来过滤信息。
当然,我们也可以通过使用Join来查询,达到同样的查询效果。
var _orderList = from c in db.Customer join o in db.Order on c.ID equals o.CustomerID where c.Name == "楚留香" select o;
那么什么情况下使用join查询、什么情况下使用导航属性查询呢?
导航属性查询就相当于SQL中的子查询。Join查询和SQL中的Inner Join查询一样,所以当两张表的数据量都大的时候就使用导航属性查询,在数据量不大的情况下使用join查询。
2.5.2 经验分享
如果模型有更新,直接选择从模型更新数据库,那么生成的SQL语句将会先判断是否存在相应的表,存在的话将先删除再创建。这样我们的操作就要很谨慎了,不然后果很严重。
在生产环境下操作更是要谨慎再谨慎,通常生产环境是做了比较严格的权限控制的,万一需要去生产环境中执行SQL脚本,就必须保证在测试环境或者开发环境中执行没问题了,然后备份数据库(开启增量备份最佳),最后执行。如果SQL执行脚本有问题,那也没办法,只能恢复数据库了。像在MySQL中,可以根据日志中的时间点和位置来进行恢复。
一般而言,我们可以把改动地方的SQL语句单独抠出来执行。如果涉及只修改模型中某个字段的微小操作,就可以直接在数据库中改过来。除此之外,没有什么好的办法。