在介绍Entity Framework的修改实体到数据库的方法之前呢,我们先简要的介绍一下ObjectContext的处理机制。
1、ObjectContext的处理机制
ObjectContext是Entity Framework封装了数据库访问的上下文,以及实体的映射关系元数据信息等。EF帮我们封装好了这么一个统一的接口。让我们所有的操作都只通过这个一个实体上下文就可以实现了增删查改等所有对应数据库的操作。当然,我们要了解EF的生成SQL的机制我们才能更好的使用EF帮我们生成效率更高的SQL脚本。
看一个实例:下图所示项目截图与实体模型图(一个简单的例子)
然后看下面一段代码:
static void Main(string[] args)
{
SchoolDBEntities schoolDB = new SchoolDBEntities();
Student student = new Student();
student.Address = "北京上地";
student.Name = "飞龙";
student.Phone = "110";
schoolDB.Student.AddObject(student);
schoolDB.SaveChanges();
}
就是简单的网数据库中添加一条数据。我们在上面代码红色背景地方加上断点【schoolDB是EF自动帮我们生成的继承自ObjectContext的上下文】并对schoolDB进行快速监视。截图如下:
由于图篇幅有限,只截取了部分视图。在此我就简单介绍一下几个比较关键的属性。
(1):Connection,相信大家一下子就能猜到,当然它封装了EF连接数据库的XxxConnection(如:SqlConnection)。这个就不啰嗦了。
(2):ObjectStateManage,它职责是维护实体类型实例和关系实例的对象状态和标识管理。也是EF上下文中非常重要的一个属性。它帮我们把添加的实体放到添加队列里,把修改的实体放到修改的队列里,当然还有删除等的。每个实体做了修改时,EF帮我们把实体放到相应的队列中并修改相应的实体的状态(EntityState),当调用ObjectContext的SaveChanges()方法时,EF根据队列的情况以及EDMX元数据映射的信息生成最终的SQL脚本。
(3):上图中我们看到_addedEntityStore就是我们刚才所说的添加实体的一个集合,而且这里面的每个实体对应的EntityState(标志实体在内存中的状态,是个Enum类型)都是Added状态,当然这表示添加,最终生成SQL时是Insert Into... 当然还有删除的队列、修改的队列这个大家自己看一下就可以了。
(4):EntityState,实体对象的状态。标志我们开发人员对实体的相应的操作,如下表格是实体的相关状态以及说明(摘自MSDN)
成员名称 | 说明 |
---|---|
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。 |
注:
对象上下文必须知道对象状态才能将更改保存回数据源。 ObjectStateEntry 对象存储 EntityState 信息。 ObjectContext 的 SaveChanges 方法根据每个对象的 EntityState 处理附加到上下文的实体和更新数据源。 有关更多信息,请参见Adding, Modifying, and Deleting Objects。
对象上下文中的对象状态由 ObjectStateManager 管理。 若要查找对象的状态,请调用以下 ObjectStateManager 方法之一:TryGetObjectStateEntry、GetObjectStateEntry 或GetObjectStateEntries。 ObjectStateEntry 的 State 属性定义该对象的状态。
总结:
EF是通过针对开发人员对实体做的修改,直接维护ObjectContext的实例中的实体操作集合并对单个实体对应的状态进行修改。最终根据此集合以及状态再加上表实体映射的元数据信息生成最终的SQL脚本。所以,我们在对应多个ObjectContext实例进行操作时要注意,调用实例自己的SaveChanges()方法时,它只会对自己实例内存空间的操作映射回数据库,而其他ObjectContext实例中的实体集合的修改都不受影响。而且EF自动帮我们做了缓存的处理,当我们第一次查询某个实体时它会自动帮我们从数据库取出数据,并装配成实体类交给我们开发人员,当第二次获取相同数据时,它会先从缓存中查找,如果已经存在数据了就立即返回,不会查询数据库。这就造成了一个问题,当ObjectContext实例如果一直不被销毁,那它的缓存会一直膨胀下去,所以在开发应用时,用单例直接处理EF的上下文也不是很合适。最好的方式应该是 在一次处理请求中(web开发)使用同一个ObjectContext实例即可,避免了多个上下文实例的维护,而且也不至于上下文实例日益膨胀。
2、EF实体中的修改
说到现在才进入正题,那我们怎么来进行修改呢?
不推荐方式一:
思路:先从ObjectContext取出实体,然后将前台传过来的DTO属性对应赋值到我们的实体上,然后调用ObjectContext的保证修改方法。
但是这种方式是最不提倡的,因为这样每次修改前都得先将数据查出来,经过SqlProfiler追踪,这么一个操作要对数据库进行两次的连接。这是不可忍受的!
推荐方式二:
思路:无需先查出实体,因为我们知道EF通过ObjectStateManage来控制添加、修改、删除队列以及实体的状态,我们所有可以通过在直接将DTO转化成实体,然后将实体对应的队列中,并且我们手动的将实体的状态处理好,再调用ObjectContext的保证修改方法,这样就避免了先查询后修改,两次数据库连接的问题了。实例代码如下:
static void Main(string[] args)
{
SchoolDBEntities schoolDB = new SchoolDBEntities();
//假设:网络传一个StudentDTO过来 ,将此DTO转化成 数据库实体
Student student = new Student();
student.Id = 1;// 假设DTO传过来的值,主键必须存在,不然会报错的
student.Address = "北京上地1";
student.Name = "飞龙1";
student.Phone = "1101"; //先将实体附加到实体上下文中
schoolDB.Student.Attach(student);
//手动修改实体的状态
schoolDB.ObjectStateManager.ChangeObjectState(student, EntityState.Modified);
//保存回数据库
schoolDB.SaveChanges();
}