• .NET基础篇——Entity Framework 数据转换层通用类


    在实现基础的三层开发的时候,大家时常会在数据层对每个实体进行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)

     1          public int Add<T>(T entity) where T : EntityObject
     2          {
     3              int changedCount = 0;
     4              try
     5              {
     6                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                 {
     8                      context.AddObject(typeof(T).Name, entity);
     9                      changedCount = context.SaveChanges();
    10                      if (changedCount > 0)
    11                          context.AcceptAllChanges();
    12                 }
    13               }
    14               catch (Exception ex)
    15               { ........ }
    16              return changedCount;
    17          }

    从下面的测试可以看到,ObjectContext.AddObject(entitySetName string,object entity)已相当成熟,它不但可以加入单个实体,也可通过导航属性,一次性加入多个关联实体。

     1          static void Main(string[] args)
     2          {
     3              BaseCommand command = new BaseCommand();
     4              //建立关联实体
     5              Company company = new Company() { CompanyName = "Sun" 
     6                     ,Address="Beijing",Telephone="010-87654321"};
     7              Position position = new Position() { PositionName = "Project Manager"
     8                     , Salary = 15000.00, Company = company };
     9              //通过Add<T>同时加入实体对象company与position
    10              int n=command.Add<Position>(position);
    11  
    12              Console.ReadKey();
    13          }

    若要使用批量插入,只要在AddObject方法前多加一个重复语言即可,在此就不再多作解释了。

     1          public int AddList<T>(List<T> entityList) where T : EntityObject
     2          {
     3                  int changedCount = 0;
     4                  try
     5                  {
     6                      using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                      {
     8                          foreach (T entity in entityList)
     9                              context.AddObject(typeof(T).Name, entity);
    10                          changedCount = context.SaveChanges();
    11                          if (changedCount > 0)
    12                              context.AcceptAllChanges();
    13                      }
    14                  }
    15                  catch (Exception ex)
    16                  { ....... }
    17                  return changedCount;
    18          }

    2. Delete

    同样地,ObjectContext 类当中也存在方法 ObjectContext.DeleteObject(object entity)用于删除实体。
    首先通过输入的参数 id 建立起EntityKey对象,然后在ObjectContext查找此实体,若实体存在则使用ObjectContext.DeleteObject(object entity)方法把此实体删除 。

     1           public int Delete<T>(int id) where T : EntityObject
     2           {
     3                   int changedCount = 0;
     4                   try
     5                   {
     6                       using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                       {
     8                           //建立EntityKey对象
     9                           EntityKey entityKey = new EntityKey(
    10                                  "BasicArchitectureEntities." + typeof(T).Name, "Id", id);
    11                           //通过EntityKey找到实体
    12                           var objResult = context.GetObjectByKey(entityKey);
    13                           //若实体存在则删除实体
    14                           if (objResult != null)
    15                               context.DeleteObject(objResult);
    16                           changedCount = context.SaveChanges();
    17                           if (changedCount > 0)
    18                               context.AcceptAllChanges();
    19                       }
    20                   }
    21                   catch (Exception ex)
    22                   { ...... }
    23                   return changedCount;
    24           }

    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对象一并删除。

     1           public int Delete<PKEntity, FKEntity>(int id)
     2               where PKEntity : EntityObject
     3               where FKEntity : EntityObject
     4           {
     5                   int changedCount = 0;
     6                   try
     7                   {
     8                       using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     9                       {
    10                           //根据软件Id建立EntityKey对象
    11                           EntityKey entityKey = new EntityKey(
    12                                   "BasicArchitectureEntities." + typeof(PKEntity).Name, "Id", id);
    13                           //根据EntityKey查找对应对象
    14                           PKEntity objResult = context.GetObjectByKey(entityKey) as PKEntity;
    15                           //根据FKEntity加载导航属性
    16                           PropertyInfo propertyInfo = typeof(PKEntity).GetProperty(
    17                                  typeof(FKEntity).Name);
    18                           EntityCollection<FKEntity> FKEntityList = propertyInfo.GetValue(
    19                                  objResult, null)  as EntityCollection<FKEntity>;
    20   
    21                           if (FKEntityList != null)
    22                               FKEntityList.Load();
    23     
    24                           if (objResult != null)
    25                               context.DeleteObject(objResult);
    26                           changedCount = context.SaveChanges();
    27   
    28                           if (changedCount > 0)
    29                               context.AcceptAllChanges();
    30                       }
    31                   }
    32                   catch (Exception ex)
    33                   { ........ }
    34                   return changedCount;
    35           }

    通过下面的方法也可根据输入的委托predicate,批量删除有关的数据。

     1          public int Delete<T>(Func<T,bool> predicate) where T: EntityObject
     2          {
     3                  int changedCount = 0;
     4                  try
     5                  {
     6                      using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                      {
     8                          //根据输入的委托查找数据
     9                          var list = context.CreateObjectSet<T>().Where(predicate);
    10                          //若存在数据,删除有关数据
    11                          if (list.Count() > 0)
    12                              foreach (var obj in list)
    13                                  context.DeleteObject(obj);
    14  
    15                          changedCount = context.SaveChanges();
    16                          if (changedCount > 0)
    17                              context.AcceptAllChanges();
    18                      }
    19                  }
    20                  catch (Exception ex)
    21                  { ...... }
    22                  return changedCount;
    23          }

    与前面的例子相同,当使用 Delete<Company>(x=>x.Id==2) 方法删除 Company 对象时,由于导航属性 Position 处于延迟加载的状态,以致系统无法实现同步删除,从而令数据出现逻辑性的错误。
    此时使用类似的方法,利用 RelatedEnd.Load() 把导航属性提前加入到上下文中,再删除Company对象时,系统就可以把对应 Position 对象一并删除。

     1           public int Delete<PKEntity, FKEntity>(Func<PKEntity,bool> predicate)
     2               where PKEntity : EntityObject
     3               where FKEntity : EntityObject
     4           {
     5                   int changedCount = 0;
     6                   try
     7                   {
     8                       using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     9                       {
    10                           //根据输入的委托查找数据
    11                           var list = context.CreateObjectSet<PKEntity>().Where(predicate);
    12                           //若数目大于0,删除有关数据
    13                           if (list.Count() > 0)
    14                           {
    15                               foreach (var obj in list)
    16                               {
    17                                   //在删除前加载其导航属性
    18                                   PropertyInfo propertyInfo = typeof(PKEntity)
    19                                          .GetProperty(typeof(FKEntity).Name);
    20                                   EntityCollection<FKEntity> FKEntityList = propertyInfo
    21                                          .GetValue(obj, null) as EntityCollection<FKEntity>;
    22                                  if (FKEntityList.Count > 0)
    23                                       FKEntityList.Load();
    24   
    25                                   context.DeleteObject(obj);
    26                               }
    27                           }
    28                           changedCount = context.SaveChanges();
    29   
    30                           if (changedCount > 0)
    31                               context.AcceptAllChanges();
    32                       }
    33                   }
    34                   catch (Exception ex)
    35                   { ....... }
    36                   return changedCount;
    37           }

    此时使用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> 方法,把新加入的实体的属性复制当前上下文。

     1      public class BaseCommand
     2      {
     3          private object o = new object();
     4          
     5          public int Update<T>(T entity) where T : EntityObject
     6          {
     7              lock (o)
     8              {
     9                      int changedCount = 0;
    10                      Type type = typeof(T);
    11 
    12                      try
    13                      {
    14                          using (BasicArchitectureEntities context = new BasicArchitectureEntities())
    15                          {
    16                              //获取实体的Id属性
    17                              PropertyInfo property = type.GetProperty("Id");
    18                              object id = property.GetValue(entity, null);
    19                              //根据Id获取上下文中的对应实体
    20                              EntityKey entityKey = new EntityKey("BasicArchitectureEntities." 
    21                                    + type.Name, "Id", id);
    22                              var objResult = context.GetObjectByKey(entityKey);
    23                              //更新实体属性
    24                              if (objResult != null)
    25                                  context.ApplyCurrentValues<T>(type.Name, entity);
    26  
    27                              changedCount = context.SaveChanges();
    28                              if (changedCount > 0)
    29                                  context.AcceptAllChanges();
    30                          }
    31                      }
    32                      catch (Exception ex)
    33                      { ... }
    34                      return changedCount;
    35              }
    36          }
    37      }

    在一对多,多对一关系时,也可以使用以下方法进行导航属性的同步更新。首先通过反射获取主实体的主键Id,然后建立EntityKey对象,再通过ObjectContext.GetObjectByKey(entityKey)方法在当前上下文当中获取此实体,最后通过 ObjectContext.ApplyCurrentValues<TEntity> 方法,把新加入的实体的属性复制当前上下文。
    下一步就是对导航属性进行更新,首先通过反射获取外键属性,然后对一对多,多对一的关系进行分别处理。在一对多关系时,把导航属性转换成EntityCollection<T2>对象集合,然后通过 ObjectContext.ApplyCurrentValues<TEntity> 方法对集合中的每个对象进行逐个更新。
    在多对一关系时,直接把导航属性转换成T2类型的对象进行更新。

     1          public int Update<T1, T2>(T1 entity)
     2              where T1 : EntityObject
     3              where T2 : EntityObject
     4          {
     5              lock (o)
     6              {
     7                      int changedCount = 0;
     8                      Type typeT1 = typeof(T1);
     9                      Type typeT2 = typeof(T2);
    10                      try
    11                      {
    12                          using (BasicArchitectureEntities context = new BasicArchitectureEntities())
    13                          {
    14                              PropertyInfo property = typeT1.GetProperty("Id");
    15                              object id = property.GetValue(entity, null);
    16  
    17                              //根据软件Id建立EntityKey对象
    18                              EntityKey entityKey = new EntityKey("BasicArchitectureEntities." 
    19                                   + typeT1.Name, "Id", id);
    20                              //根据EntityKey查找对应对象
    21                              T1 objT1 = context.GetObjectByKey(entityKey) as T1;
    22                              //在上下文中更新当前对象
    23                              if (objT1 != null)
    24                                  context.ApplyCurrentValues<T1>(typeT1.Name, entity);
    25  
    26                              //获取外键属性
    27                              PropertyInfo propertyInfo = typeT1.GetProperty(typeT2.Name);
    28  
    29                              //在一对多关键时更新导航属性
    30                              var T2List = propertyInfo.GetValue(entity, null) 
    31                                     as EntityCollection<T2>;
    32                              if (T2List != null)
    33                              {
    34                                  foreach (var obj in T2List.ToList())
    35                                  {
    36                                      var oldEntity = context.GetObjectByKey(obj.EntityKey);
    37                                      if (oldEntity != null)
    38                                          context.ApplyCurrentValues<T2>(typeT2.Name, obj);
    39                                  }
    40                              }
    41  
    42                              //在多对一,一对一关系时更新导航属性
    43                              var objT2 = propertyInfo.GetValue(entity, null) as T2;
    44                              if (objT2!= null)
    45                              {
    46                                  var oldEntity = context.GetObjectByKey(objT2.EntityKey);
    47                                  if (oldEntity != null)
    48                                      context.ApplyCurrentValues<T2>(typeT2.Name, objT2);
    49                              }
    50  
    51                              changedCount = context.SaveChanges();
    52                              if (changedCount > 0)
    53                                  context.AcceptAllChanges();
    54                      }
    55                      catch (Exception ex)
    56                      { ...... }
    57                      return changedCount;
    58              }
    59          }

    通过此方法,无论你要通过Company同步更新Position,还是反过来通过Position同步更新Company,系统也能正常运行。

    4. Read

    Read 是CRUD中最常见的,下面就为大家介绍最通用的几种方法

    4.1 通过Id获取单个实体

     1         public T GetObject<T>(int id) where T : EntityObject
     2         {
     3             try
     4             {
     5                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     6                 {
     7                     EntityKey entityKey = new EntityKey("BasicArchitectureEntities." 
     8                           + typeof(T).Name, "Id", id);
     9                     var objResult = context.GetObjectByKey(entityKey);
    10                     return objResult as T;
    11                 }
    12             }
    13             catch (Exception ex)
    14             {
    15                 return null;
    16             }
    17         }

    4.2 通过输入的Func<T,bool>委托获取对象

     1         public T GetObject<T>(Func<T,bool> predicate) where T : EntityObject
     2         {
     3             try
     4             {
     5                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     6                 {
     7                     var objectSet = context.CreateObjectSet<T>().Where(predicate);
     8                     if (objectSet.Count() > 0)
     9                         return objectSet.First();
    10                     else
    11                         return null;
    12                 }
    13             }
    14             catch (Exception ex)
    15             {
    16                 return null;
    17             }
    18         }

    4.3通过输入的Func<T,bool>委托获取对象,并同时加载单个导航属性

     1         public T GetObject<T>(Func<T, bool> predicate,string includePath) 
     2             where T : EntityObject
     3         {
     4             try
     5             {
     6                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                 {
     8                     var objectQuery = context.CreateObjectSet<T>()
     9                         .Include(includePath)
    10                         .Where(predicate);
    11 
    12                     if (objectQuery.Count() > 0)
    13                         return objectQuery.First();
    14                     else
    15                         return null;
    16                 }
    17             }
    18             catch (Exception ex)
    19             {
    20                 return null;
    21             }
    22         }

    4.4通过输入的Func<T,bool>委托获取对象,并同时加载多个导航属性

     1         public T GetObject<T>(Func<T, bool> predicate, string[] includePath)
     2              where T : EntityObject
     3         {
     4             try
     5             {
     6                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                 {
     8                     var list = context.CreateObjectSet<T>().Where("1==1");
     9 
    10                     foreach (var path in includePath)
    11                         list=list.Include(path);
    12 
    13                     var returnValue = list.Where(predicate).ToList();
    14 
    15                     if (returnValue.Count() > 0)
    16                         return returnValue.First();
    17                     else
    18                         return null;
    19                 }
    20             }
    21             catch (Exception ex)
    22             {
    23                 return null;
    24             }
    25         }

    4.5 通过输入的Func<T,bool>委托获取对象集合

     1         public IList<T> GetList<T>(Func<T,bool> func) where T:EntityObject
     2         {
     3             try
     4             {
     5                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     6                 {
     7                     ObjectSet<T> objectSet = context.CreateObjectSet<T>();
     8                     IList<T> list = objectSet.Where(func).ToList();
     9                     return list;
    10                 }
    11             }
    12             catch (Exception ex)
    13             {
    14                 return null;
    15             }
    16         }

    4.6通过输入的Func<T,bool>委托获取对象集合,并同时加入单个导航属性

     1         public IList<T> GetList<T>(Func<T, bool> func,string includePath)
     2              where T : EntityObject
     3         {
     4             try
     5             {
     6                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                 {
     8                     ObjectSet<T> objectSet = context.CreateObjectSet<T>();
     9                     IList<T> list = objectSet.Include(includePath).Where(func).ToList();
    10                     return list;
    11                 }
    12             }
    13             catch (Exception ex)
    14             {
    15                 return null;
    16             }
    17         }

    4.7通过输入的Func<T,bool>委托获取对象集合,并同时加入多个导航属性

     1         public IList<T> GetList<T>(Func<T, bool> func, string[] includePath)
     2             where T : EntityObject
     3         {
     4             try
     5             {
     6                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     7                 {
     8                     var list = context.CreateObjectSet<T>().Where("1==1");
     9                     foreach (var path in includePath)
    10                         list = list.Include(path);
    11                     return list.Where(func).ToList();
    12                 }
    13             }
    14             catch (Exception ex)
    15             {
    16                 return null;
    17             }
    18         }

    4.8 通过原始的SqlCommandText获取对象集

     1         public IList<T> GetList<T>(string commandText)
     2         {
     3             try
     4             {
     5                 using (BasicArchitectureEntities context = new BasicArchitectureEntities())
     6                 {
     7                     IList<T> list = context.ExecuteStoreQuery<T>(commandText).ToList();
     8                     return list;
     9                 }
    10             }
    11             catch (Exception ex)
    12             {
    13                 return null;
    14             }
    15         }

    只能完成这一个DAL层的通用类以后,您就可在CompanyDAL、PersonDAL、PositionDAL ...... 等多个类中调用这个通用类,轻松地完成各项CRUD的操作。

     1     public class CompanyDAL:ICompanyDAL
     2     {
     3         private BaseCommand command = new BaseCommand();
     4 
     5         public int AddCompany(Company company)
     6         {
     7             return command.Add<Company>(company);
     8         }
     9 
    10         public int DeleteCompany(int id)
    11         {
    12             return command.Delete<Company>(id);
    13         }
    14 
    15         public int UpdateComapny(Company company)
    16         {
    17             return command.Update<Company>(company);
    18         }
    19         .............
    20     }

    相比起以往的SqlCommand操作,Entity Framework更体现出映射的灵活性。以往的操作中,即使开发出一个通用类,CommandText 通常都需要使用手工输入,特别是重复的Update命令操作中,往往令人不厌其烦。通过Entity Framework可以把CRUD更高度地集中在一个通用类,令开发变得更加简单。
    希望本篇文章对您的系统开发有所帮助。

    对软件架构开发有兴趣的朋友欢迎加入博客园讨论组 “.NET高级编程”

    对 .NET 开发有兴趣的朋友欢迎加入QQ群:162338858 同探讨 !

    作者:风尘浪子
    http://www.cnblogs.com/leslies2/archive/2012/05/16/2504673.html

    原创作品,转载时请注明作者及出处






     

  • 相关阅读:
    maven工程的目录结构
    集合的区别
    名词解析
    1.(字符串)-判断字符串是否是子集字符串
    1.(字符串)-判断两字符串是否相等
    python max函数技巧
    1.(字符串)-子字符串位置查找
    numpy线性代数np.linalg
    Python图像库PIL 使用
    pyhthon-chr
  • 原文地址:https://www.cnblogs.com/leslies2/p/2504673.html
Copyright © 2020-2023  润新知