在实现基础的三层开发的时候,大家时常会在数据层对每个实体进行CRUD的操作,其中存在相当多的重复代码。为了减少重复代码的出现,通常都会定义一个共用类,实现相似的操作,下面为大家介绍一下Entity Framework时常用到的通用类。
首先在数据库建立起几个关联表:Person、Company、Position,三个实体之间通过导航属性进行相互引用。
下面为大家分别介绍以泛型实现的 Create、Read、Update、Delete 操作:
1. Create
在ObjectContext类之中,早已经为大家预定了一个Create 的操作 AddObject:
void ObjectContext.AddObject(entitySetName string,object entity)
void ObjectSet<T>.AddObject(T entity)
从下面的测试可以看到,ObjectContext.AddObject(entitySetName string,object entity)已相当成熟,它不但可以加入单个实体,也可通过导航属性,一次性加入多个关联实体。
若要使用批量插入,只要在AddObject方法前多加一个重复语言即可,在此就不再多作解释了。
2. Delete
同样地,ObjectContext 类当中也存在方法 ObjectContext.DeleteObject(object entity)用于删除实体。
首先通过输入的参数 id 建立起EntityKey对象,然后在ObjectContext查找此实体,若实体存在则使用ObjectContext.DeleteObject(object entity)方法把此实体删除 。
ObjectContext.DeleteObject(object entity)与ObjectContext.AddObject(entitySetName string,object entity)相同,可以通过导航属性,一次性删除多个关联实体。但如果数据库中存在下面的数据
Company表:
Position表:
此时使用此 int Delete<Company>(2) 方法删除Company对象,系统将会报错。这是由于导航属性在默认情况下具有延时加载的特性,在系统使用ObjectContext.GetObjectByKey(entityKey)方法加载实体时,它的导航属性不会马上加载到上下文当中。而是在调用该导航属性时,对象才会被加载。
因而系统通过ObjectContext.GetObjectByKey(2)获取Company对象时,对应的Position对象并未被加载到上下文当中,所以当删除Company对象时,Position对象不能被同步删除,因而造成逻辑上的错误。为解决这一问题,可以利用RelatedEnd.Load()方法提前加载导航属性。
RelatedEnd是EntityCollection<TEntity> 、EntityReference的父类,它们是特定实体类型的对象集合,该实体类型表示一对多、多对一、多对多的关系。而RelatedEnd.Load()方法,可以将一个或多个相关对象提前加载到相关实体当中。
首先通过ObjectContext.GetObjectByKey(entityKey)方法找到Company对象,然后利用反射属性PropertyInfo类获取导 航属性Position,最后使用RelatedEnd.Load()方法,把导航属性加载到当前上下文中。此时使用 Delete<Company,Position>(2)方法删除Company对象时,系统将能正常运行,并把对应的Position对象 一并删除。
通过下面的方法也可根据输入的委托predicate,批量删除有关的数据。
与前面的例子相同,当使用 Delete<Company>(x=>x.Id==2) 方法删除 Company 对象时,由于导航属性 Position 处于延迟加载的状态,以致系统无法实现同步删除,从而令数据出现逻辑性的错误。
此时使用类似的方法,利用 RelatedEnd.Load() 把导航属性提前加入到上下文中,再删除Company对象时,系统就可以把对应 Position 对象一并删除。
此时使用Delete<Company,Position>(x=>x.Id==2),这样就可以把Company对象和相关的Position对象同时删除。
3. Update
ObjectContext 中存在方法 ObjectContext.ApplyCurrentValues<TEntity> 和 ObjectContext.ApplyOriginalValues<TEntity>,用于把将标量值从实体复制到 ObjectContext 中具有相同主键的对象集中。
注意:在调用此方法前必须把实体预先加载到当前上下文当中,要不然系统将会显示 “objectstatemanager 无法跟踪具有相同键的多个对象” 的错误。
由于DAL层的对象大部分使用单体模式进行开发,而BaseCommand是一个共 用对象,在共同操作时,Create、Delete、Read 等操作一般不会对实体造成逻辑性的影响。但如果有多个实体同时调用 Update 操作,就有可能对实体造成逻辑性影响。为了避免这一事件的发生,此处使用方法锁定的模式,以 lock(object) 锁定某一对象,以确保在同一时间内只会对一个实体进行更新。
首先通过反射方式获取对象的Id,然后通过 ObjectContext.GetObjectByKey(entityKey) 方法把实体加载到当前上下文当中,最后利用 ObjectContext.ApplyCurrentValues<TEntity> 方法,把新加入的实体的属性复制当前上下文。
在一对多,多对一关系时,也可以使用以下方法进行导航属性的同步更新。首先通过反射获取主实体的主键Id,然后建立EntityKey对象,再通过 ObjectContext.GetObjectByKey(entityKey)方法在当前上下文当中获取此实体,最后通过 ObjectContext.ApplyCurrentValues<TEntity> 方法,把新加入的实体的属性复制当前上下文。
下一步就是对导航属性进行更新,首先通过反射获取外键属性,然后对一对多,多对一的关系进行分别处理。在一对多关系时,把导航属性转换成EntityCollection<T2>对象集合,然后通过 ObjectContext.ApplyCurrentValues<TEntity> 方法对集合中的每个对象进行逐个更新。
在多对一关系时,直接把导航属性转换成T2类型的对象进行更新。
通过此方法,无论你要通过Company同步更新Position,还是反过来通过Position同步更新Company,系统也能正常运行。
4. Read
Read 是CRUD中最常见的,下面就为大家介绍最通用的几种方法
4.1 通过Id获取单个实体
4.2 通过输入的Func<T,bool>委托获取对象
4.3通过输入的Func<T,bool>委托获取对象,并同时加载单个导航属性
4.4通过输入的Func<T,bool>委托获取对象,并同时加载多个导航属性
4.5 通过输入的Func<T,bool>委托获取对象集合
4.6通过输入的Func<T,bool>委托获取对象集合,并同时加入单个导航属性
4.7通过输入的Func<T,bool>委托获取对象集合,并同时加入多个导航属性
4.8 通过原始的SqlCommandText获取对象集
只能完成这一个DAL层的通用类以后,您就可在CompanyDAL、PersonDAL、PositionDAL ...... 等多个类中调用这个通用类,轻松地完成各项CRUD的操作。
相比起以往的SqlCommand操作,Entity Framework更体现出映射的灵活性。以往的操作中,即使开发出一个通用类,CommandText 通常都需要使用手工输入,特别是重复的Update命令操作中,往往令人不厌其烦。通过Entity Framework可以把CRUD更高度地集中在一个通用类,令开发变得更加简单。
希望本篇文章对您的系统开发有所帮助。