Entity Framework学习笔记(三)----CRUD(2)
请注明转载地址:http://www.cnblogs.com/arhat
昨天晚上老魏配的机器终于到了,可是拿回来之后什么都组装好了,唯独差一个非常重要的组件”电源线”,老魏那个汗啊。于是从朋友那里借来一根电源线终于折腾到凌晨两点多才把所有的软件和系统装好,今天早上顺便装了一个MAC(嘻嘻)。最让老魏兴奋的是换了VS2013,在使用VS2013的时候感觉非常的不错,对EF的支持真的是大大的增强了,不用再跑到网上找这个插件那个插件的,直接通过T4文件来生成Context是实体类了。那么今天老魏就用VS2013和SQL Server2008来继续学习EF把,至于创建的过程老魏在第一章中已经介绍过了,基本过程是一样的。前两章老魏用的是MySQL,那么本章用的是SQL Server,本质上是一样的,老魏就是把MySQl中的数据导入到SQL Server中。
说到这里了,老魏不得不提一下在SQL Server表设计器中是无法对表建立多对多关系的,需要到数据库关系图中建立,这点很让老魏不爽!更让了老魏不爽的是使用HeidiSQL竟然无法对SQL Server建立表关系,我那个擦啊!弄得老魏都崩溃了,如果各位朋友那个有好一点的SQL Server的客户端工具可以推荐一下。
好了,开始本章的内容!在上一章中我们只是对Clazz,Student两个表进行一个单独的插入,删除和更新以及简单的使用了Where扩展方法来得到数据。那么本章将会继续对这两个表的操作来进一步的说明,因为在本章主要是利用EF的懒加载来对数据操作。好了,看看本章的第一个需求。
如果我们想在插入班级的时候就把改班级的学生也插入到数据库中,这样可以不可以呢?答案是可以的,其实在平时我们的项目中就已经有这样的需求,那么下面我们来看看用EF怎么实现呢!
更改Program.cs代码如下:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //定义一个Student实体对象 Model.Student student = new Model.Student() { SName="大话济公",SAge=56,SMail="single_jie@163.com"}; //定义一个Clazz实体对象,这实体对象是要插入到数据库中的 Model.Clazz clazz = new Model.Clazz() { CName="泡妞课程"}; //通过Clazz的导航属性把student对象加入到Clazz的Students集合中 //这点EF的导航属性做的非常不错,传统的ADO.NET实现起来比较麻烦 clazz.Students.Add(student); //更改数据库 context.SaveChanges(); }
当我们执行的时候没有发现问题产生,难道真得能够插入到数据中吗?我们打开数据库来看看结果,验证一下我们的猜想。
发现在数据库中Student表和Clazz表都已经正确的插入了,那么说明没有问题,但是我们在高兴的同时要多思考一下。如果是在插入学生的时候能不能直接选择一个班级呢?这个问题是在项目中经常操作的,比如发表一篇文章的时候要选择分类。
好,我们既然发现了问题,那么我们在更改一下代码:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //定义一个Student实体对象 Model.Student student = new Model.Student() { SName="广亮和尚",SAge=34,SMail="guangliang@163.com"}; //定义一个Clazz实体对象,这实体对象是要插入到数据库中的 Model.Clazz clazz = new Model.Clazz() { CName="泡妞课程"}; //通过导航属性把clazz对象附加到student实体对象上 student.Clazz = clazz; //把clazz对象加入到Clazz context.Student.Add(student); //更改数据库 context.SaveChanges(); }
同样的运行没有任何的问题,但是当我们查看数据的时候却发现问题来了。
各位发现了什么?学生能够正常的插入,而班级则重复的插入了,按照我们正常的思维者这条记录是不应该插入到数据库中,其实这不该怪EF的,这是我们的业务逻辑有问题了,那么下面我们更改一下代码:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //定义一个Student实体对象 Model.Student student = new Model.Student() { SName="飞龙僧",SAge=56,SMail="feilong@163.com"}; //得到一个Clazz实体对象 Model.Clazz clazz = context.Clazz.Where<Model.Clazz>(c => c.CId == 1).Take<Model.Clazz>(1).FirstOrDefault<Model.Clazz>(); //通过导航属性把clazz对象附加到student实体对象上 student.Clazz = clazz; //把clazz对象加入到Clazz context.Student.Add(student); //更改数据库 context.SaveChanges(); }
运行一下,发现没有问题,当我们再次查看数据的时候,发现数据时正常的,在Clazz表中并没有插入新的纪录(废话,因为我们是查出来的嘛!)。
但是此时我们有生出了一个疑问,在想Student表中插入数据的时候,我们只需要一个CId,但这里我们却是给student实体对象一个clazz对象,这样不是浪费了内存了吗?其实不然,微软比我们聪明肯定想到了。其实在赋值的时候,只是把CId属性的值赋值给了Student的CId了。下面是生成的SQL语句
INSERT [dbo].[Student]([SName], [SAge], [SMail], [CId]) VALUES (@0, @1, @2, @3) SELECT [SId] FROM [dbo].[Student] WHERE @@ROWCOUNT > 0 AND [SId] = scope_identity()',N'@0 varchar(50),@1 int,@2 varchar(50),@3 int',@0='飞龙僧',@1=56,@2='feilong@163.com',@3=1
本来嘛,使用EF只是为了能够更简单的操作数据库,那么肯定是要牺牲一点效率的,这点就不用考虑了,现在的服务器的配置都那么高了,根本不在乎这点内存的浪费。
现在我们如果要更新一个学生的班级该如何做呢?看看代码:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); //得到一个Student实体对象 Model.Student student = context.Student.Where<Model.Student>(s=>s.SId==3).Take<Model.Student>(1).FirstOrDefault<Model.Student>(); //定义一个Clazz实体对象,这实体对象是要插入到数据库中的 Model.Clazz clazz = context.Clazz.Where<Model.Clazz>(c => c.CId == 10).Take<Model.Clazz>(1).FirstOrDefault<Model.Clazz>(); //通过导航属性把clazz对象附加到student实体对象上 student.Clazz = clazz; //更改数据库 context.SaveChanges(); }
运行一下,正常运行,在数据中同样也到了争取的结果了。但是我们现在又有问题诞生了,在更新的时候,我们是先查询出来让后再更新到数据库,这样的话就对数据库操作了两次,显然不是我们想要的效果,那么该怎么来解决这问题呢,也就是不用先查询然后在更新呢?答案是可以的!
在说这个问题的时候,老魏在这里得给大家说一下抱歉了,由于前两章的内容是MySql EF5的版本的,所以和本节EF6的内容稍微的优点不同,所以这里呢,老魏使用EF6的方法把。
比如现在我们有如下的代码:
static void Main(string[] args) { //EF 上下文 DAL.SchoolContext context = new DAL.SchoolContext(); Model.Student student = new Model.Student(); //在使用这中方法的时候,必须保证主键在数据库中必须存在 student.SId = 4; student.SName = "飞龙僧"; student.SAge = 34; student.CId = 1; student.SMail = "flydragon@ww.com"; //通过DbEntityEntry来跟踪student的状态,通过修改状态值,来更新数据 context.Entry(student).State = EntityState.Modified; //更改数据库 context.SaveChanges(); }
运行一下,在数据总的确放生改变了,那么在这里出现的Entry又是个什么东西呢?根据微软的解释是:“DbEntityEntry此类的实例提供对有关由 DbContext 跟踪的实体的信息和控制的访问权。 使用上下文的 Entity 或 Entities 方法来获取此类型的对象。“什么意思呢?其实Entity Framworke再把实体更新到数据库中(增删改查),是根据实体的状态来确定的,那么实体的状态又是由DbEntityEntry这个类的对象来保存和设置,但同时还起到一个非常重要的作用,那就是把Entity(这里是student)加入到context中,如果Entity不在context中,那么context是无法更新数据的,所以context.Entry(Entity)就是把实体加入到context中进行管理,同时更改一下他的状态,让context知道如何去处理这个Entity.当调用SaveChanges方法时候,context就根据state来处理数据。
EntityState,实体对象的状态。标志我们开发人员对实体的相应的操作,如下表格是实体的相关状态以及说明。
成员名称 | 说明 |
Detached | 对象存在,但没有被跟踪。 在创建实体之后、但将其添加到对象上下文之前,该实体处于此状态。 An entity is also in this state after it has been removed from the context by calling the Detach method or if it is loaded by using a NoTrackingMergeOption. 没有 ObjectStateEntry 实例与状态为 Detached 的对象关联。 |
Unchanged | 自对象附加到上下文中后,或自上次调用 SaveChanges 方法后,此对象尚未经过修改。 |
Added | 对象为新对象,并且已添加到对象上下文,但尚未调用 SaveChanges 方法。 在保存更改后,对象状态将更改为 Unchanged。 状态为 Added 的对象在 ObjectStateEntry 中没有原始值。 |
Deleted | 对象已从对象上下文中删除。 在保存更改后,对象状态将更改为 Detached。 |
Modified | 对象上的一个标量属性已更改,但尚未调用 SaveChanges 方法。 在不带更改跟踪代理的 POCO 实体中,调用 DetectChanges 方法时,已修改属性的状态将更改为 Modified。 在保存更改后,对象状态将更改为 Unchanged。 |
对象上下文必须知道对象状态才能将更改保存回数据源。虽然这样我们可以少操作一次数据库,但是由于必须保证的是主键在数据库中必须存在,所以这点我们在写程序的时候是很难保证的,所以老魏建议还是使用原来的方法,当然了这个得根据不同情况选用不同的方法,有的人推荐使用EntityState,有的人推荐使用查询更新,具体情况及具体分析吧,但是在这里老魏的说一下,如果要更改实体的某些属性而不是全部属性的时候,我们这个时候得使用EntityState了。
好了,到此,关于Entity Framework的CRUD的操作说明老魏就说到这里了,只能起到一个抛砖引玉的作用,希望大家能够从中得到点什么东西吧!