成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

EF Code First:數(shù)據(jù)更新最佳實踐

開發(fā) 架構(gòu)
最近在整理EntityFramework數(shù)據(jù)更新的代碼,頗有體會,覺得有分享的價值,于是記錄下來,讓需要的人少走些彎路也是好的。為方便起見,先創(chuàng)建一個控制臺工程,使用using(var db = new DataContext)的形式來一步一步講解EF數(shù)據(jù)更新的可能會遇到的問題及對應(yīng)的解決方案。在獲得最佳方案之后,再整合到本系列的代碼中。

一、前言

最近在整理EntityFramework數(shù)據(jù)更新的代碼,頗有體會,覺得有分享的價值,于是記錄下來,讓需要的人少走些彎路也是好的。

為方便起見,先創(chuàng)建一個控制臺工程,使用using(var db = new DataContext)的形式來一步一步講解EF數(shù)據(jù)更新的可能會遇到的問題及對應(yīng)的解決方案。在獲得最佳方案之后,再整合到本系列的代碼中。

本示例中,用到的數(shù)據(jù)模型如下圖所示:

  1. 部門:一個部門可有多個角色【1-N】
  2. 角色:一個角色必有一個部門【N-1】,一個角色可有多個人員【N-N】
  3. 人員:一個人員可有多個角色【N-N】

并且,我們通過數(shù)據(jù)遷移策略初始化了一些數(shù)據(jù):

初始化數(shù)據(jù)

  1. protected override void Seed(GmfEFUpdateDemo.Models.DataContext context)  
  2. {  
  3.     //部門  
  4.     var departments = new []  
  5.     {  
  6.         new Department {Name = "技術(shù)部"},  
  7.         new Department {Name = "財務(wù)部"}  
  8.     };  
  9.     context.Departments.AddOrUpdate(m => new {m.Name}, departments);  
  10.     context.SaveChanges();  
  11.  
  12.     //角色  
  13.     var roles = new[]  
  14.     {  
  15.         new Role{Name = "技術(shù)部經(jīng)理", Department = context.Departments.Single(m=>m.Name =="技術(shù)部")},  
  16.         new Role{Name = "技術(shù)總監(jiān)", Department = context.Departments.Single(m=>m.Name =="技術(shù)部")},  
  17.         new Role{Name = "技術(shù)人員", Department = context.Departments.Single(m=>m.Name =="技術(shù)部")},  
  18.         new Role{Name = "財務(wù)部經(jīng)理", Department = context.Departments.Single(m=>m.Name =="財務(wù)部")},  
  19.         new Role{Name = "會計", Department = context.Departments.Single(m=>m.Name =="財務(wù)部")}  
  20.     };  
  21.     context.Roles.AddOrUpdate(m=>new{m.Name}, roles);  
  22.     context.SaveChanges();  
  23.  
  24.     //人員  
  25.     var members = new[]  
  26.     {  
  27.         new Member  
  28.         {  
  29.             UserName = "郭明鋒",  
  30.             Password = "123456",  
  31.             Roles = new HashSet<Role>  
  32.             {  
  33.                 context.Roles.Single(m => m.Name == "技術(shù)人員")  
  34.             }  
  35.         }  
  36.     };  
  37.     context.Members.AddOrUpdate(m => new {m.UserName}, members);  
  38.     context.SaveChanges();  

二、整體更新(不考慮更新屬性)

情景一:同一上下文中數(shù)據(jù)取出來更新后直接保存:

代碼:

  1. private static void Method01()  
  2. {  
  3.     using (var db = new DataContext())  
  4.     {  
  5.         const string userName = "郭明鋒";  
  6.         Member oldMember = db.Members.Single(m => m.UserName == userName);  
  7.         Console.WriteLine("更新前:{0}。", oldMember.AddDate);  
  8.  
  9.         oldMember.AddDate = oldMember.AddDate.AddMinutes(10);  
  10.         int count = db.SaveChanges();  
  11.         Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  12.  
  13.         Member newMember = db.Members.Single(m => m.UserName == userName);  
  14.         Console.WriteLine("更新后:{0}。", newMember.AddDate);  
  15.     }  

代碼解析:操作必然成功,執(zhí)行的sql語句如下:

  1. exec sp_executesql N'update [dbo].[Members]  
  2. set [AddDate] = @0  
  3. where ([Id] = @1)  
  4. ',N'@0 datetime2(7),@1 int',@0='2013-08-31 13:17:33.1570000',@1=1 

注意,這里并沒有對更新實體的屬性進(jìn)行篩選,但EF還是聰明的生成了只更新AddDate屬性的sql語句。

情景二:從上下文1中取出數(shù)據(jù)并修改,再在上下文2中進(jìn)行保存:

 代碼:

  1. private static void Method02()  
  2. {  
  3.     const string userName = "郭明鋒";  
  4.  
  5.     Member updateMember;  
  6.     using (var db1 = new DataContext())  
  7.     {  
  8.         updateMember = db1.Members.Single(m => m.UserName == userName);  
  9.     }  
  10.     updateMember.AddDate = DateTime.Now;  
  11.  
  12.     using (var db2 = new DataContext())  
  13.     {  
  14.         db2.Members.Attach(updateMember);  
  15.         DbEntityEntry<Member> entry = db2.Entry(updateMember);  
  16.         Console.WriteLine("Attach成功后的狀態(tài):{0}", entry.State); //附加成功之后,狀態(tài)為EntityState.Unchanged  
  17.         entry.State = EntityState.Modified;  
  18.         int count = db2.SaveChanges();  
  19.         Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  20.  
  21.         Member newMember = db2.Members.Single(m => m.UserName == userName);  
  22.         Console.WriteLine("更新后:{0}。", newMember.AddDate);  
  23.     }  

代碼解析:對于db2而言,updateMemner是一個全新的外來的它不認(rèn)識的對象,所以需要使用Attach方法把這個外來對象附加到它的上下文中,Attach之后,實體的對象為 EntityState.Unchanged,如果不改變狀態(tài),在SaveChanged的時候?qū)⑹裁匆膊蛔觥R虼诉€需要把狀態(tài)更改為EntityState.Modified,而由Unchanged -> Modified的改變,是我們強(qiáng)制的,而不是由EF狀態(tài)跟蹤得到的結(jié)果,因而EF無法分辨出哪個屬性變更了,因而將不分青紅皂白地將所有屬性都刷一遍,執(zhí)行如下sql語句:

  1. exec sp_executesql N'update [dbo].[Members]  
  2. set [UserName] = @0, [Password] = @1, [AddDate] = @2, [IsDeleted] = @3  
  3. where ([Id] = @4)  
  4. ',N'@0 nvarchar(50),@1 nvarchar(50),@2 datetime2(7),@3 bit,@4 int',@0=N'郭明鋒',@1=N'123456',@2='2013-08-31 13:28:01.9400328',@3=0,@4=1 

情景三:在情景二的基礎(chǔ)上,上下文2中已存在與外來實體主鍵相同的數(shù)據(jù)了

代碼:

  1. private static void Method03()  
  2. {  
  3.     const string userName = "郭明鋒";  
  4.  
  5.     Member updateMember;  
  6.     using (var db1 = new DataContext())  
  7.     {  
  8.         updateMember = db1.Members.Single(m => m.UserName == userName);  
  9.     }  
  10.     updateMember.AddDate = DateTime.Now;  
  11.  
  12.     using (var db2 = new DataContext())  
  13.     {  
  14.         //先查詢一次,讓上下文中存在相同主鍵的對象  
  15.         Member oldMember = db2.Members.Single(m => m.UserName == userName);  
  16.         Console.WriteLine("更新前:{0}。", oldMember.AddDate);  
  17.  
  18.         db2.Members.Attach(updateMember);  
  19.         DbEntityEntry<Member> entry = db2.Entry(updateMember);  
  20.         Console.WriteLine("Attach成功后的狀態(tài):{0}", entry.State); //附加成功之后,狀態(tài)為EntityState.Unchanged  
  21.         entry.State = EntityState.Modified;  
  22.         int count = db2.SaveChanges();  
  23.         Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  24.  
  25.         Member newMember = db2.Members.Single(m => m.UserName == userName);  
  26.         Console.WriteLine("更新后:{0}。", newMember.AddDate);  
  27.     }  

代碼解析:此代碼與情景二相比,就是多了14~16三行代碼,目的是制造一個要更新的數(shù)據(jù)在上下文2中正在使用的場景,這時會發(fā)生什么情況呢?

當(dāng)代碼執(zhí)行到18行的Attach的時候,將引發(fā)一個EF數(shù)據(jù)更新時非常常見的異常:

  1. 捕捉到 System.InvalidOperationException  
  2.   HResult=-2146233079  
  3.   Message=ObjectStateManager 中已存在具有同一鍵的對象。ObjectStateManager 無法跟蹤具有相同鍵的多個對象。  
  4.   Source=System.Data.Entity  
  5.   StackTrace:  
  6.        在 System.Data.Objects.ObjectContext.VerifyRootForAdd(Boolean doAttach, String entitySetName, IEntityWrapper wrappedEntity, EntityEntry existingEntry, EntitySet& entitySet, Boolean& isNoOperation)  
  7.        在 System.Data.Objects.ObjectContext.AttachTo(String entitySetName, Object entity)  
  8.        在 System.Data.Entity.Internal.Linq.InternalSet`1.<>c__DisplayClass2.<Attach>b__1()  
  9.        在 System.Data.Entity.Internal.Linq.InternalSet`1.ActOnSet(Action action, EntityState newState, Object entity, String methodName)  
  10.        在 System.Data.Entity.Internal.Linq.InternalSet`1.Attach(Object entity)  
  11.        在 System.Data.Entity.DbSet`1.Attach(TEntity entity)  
  12.        在 GmfEFUpdateDemo.Program.Method03() 位置 d:\Documents\Visual Studio 2012\Projects\GmfEFUpdateDemo\GmfEFUpdateDemo\Program.cs:行號 148  
  13.        在 GmfEFUpdateDemo.Program.Main(String[] args) 位置 d:\Documents\Visual Studio 2012\Projects\GmfEFUpdateDemo\GmfEFUpdateDemo\Program.cs:行號 54  
  14.   InnerException: 

原因正是上下文2中已經(jīng)有了一個相同主鍵的對象,不能再附加了。

 這應(yīng)該是一個非常常見的場景,也就是必須想辦法解決的場景。其實只要獲得現(xiàn)有實體數(shù)據(jù)的跟蹤,再把新數(shù)據(jù)賦到現(xiàn)有實體上,就可以解決問題了,此方法唯一的缺點就是要獲取到舊的實體數(shù)據(jù)。代碼如下:

  1. private static void Method04()  
  2. {  
  3.     const string userName = "郭明鋒";  
  4.  
  5.     Member updateMember;  
  6.     using (var db1 = new DataContext())  
  7.     {  
  8.         updateMember = db1.Members.Single(m => m.UserName == userName);  
  9.     }  
  10.     updateMember.AddDate = DateTime.Now;  
  11.  
  12.     using (var db2 = new DataContext())  
  13.     {  
  14.         //先查詢一次,讓上下文中存在相同主鍵的對象  
  15.         Member oldMember = db2.Members.Single(m => m.UserName == userName);  
  16.         Console.WriteLine("更新前:{0}。", oldMember.AddDate);  
  17.  
  18.         DbEntityEntry<Member> entry = db2.Entry(oldMember);  
  19.         entry.CurrentValues.SetValues(updateMember);  
  20.         int count = db2.SaveChanges();  
  21.         Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  22.  
  23.         Member newMember = db2.Members.Single(m => m.UserName == userName);  
  24.         Console.WriteLine("更新后:{0}。", newMember.AddDate);  
  25.     }  

代碼中的18~19行是核心代碼,先從上下文中的舊實體獲取跟蹤,第19行的SetValues方法就是把新值設(shè)置到舊實體上(這一條很強(qiáng)大,支持任何類型,比如ViewObject,DTO與POCO可以直接映射傳值)。由于值的更新是直接在上下文中的現(xiàn)有實體上進(jìn)行的,EF會自己跟蹤值的變化,因此這里并不需要我們來強(qiáng)制設(shè)置狀態(tài)為Modified,執(zhí)行的sql語句也足夠簡單:

  1. exec sp_executesql N'update [dbo].[Members]  
  2. set [AddDate] = @0  
  3. where ([Id] = @1)  
  4. ',N'@0 datetime2(7),@1 int',@0='2013-08-31 14:03:27.1425875',@1=1 

#p#

整體更新的最佳實現(xiàn)

綜合上面的幾種情景,我們可以得到EF對實體整體更新的最佳方案,這里寫成DbContext的擴(kuò)展方法,代碼如下:

  1. public static void Update<TEntity>(this DbContext dbContext, params TEntity[] entities) where TEntity : EntityBase  
  2. {  
  3.     if (dbContext == nullthrow new ArgumentNullException("dbContext");  
  4.     if (entities == nullthrow new ArgumentNullException("entities");  
  5.  
  6.     foreach (TEntity entity in entities)  
  7.     {  
  8.         DbSet<TEntity> dbSet = dbContext.Set<TEntity>();  
  9.         try 
  10.         {  
  11.             DbEntityEntry<TEntity> entry = dbContext.Entry(entity);  
  12.             if (entry.State == EntityState.Detached)  
  13.             {  
  14.                 dbSet.Attach(entity);  
  15.                 entry.State = EntityState.Modified;  
  16.             }  
  17.         }  
  18.         catch (InvalidOperationException)  
  19.         {  
  20.             TEntity oldEntity = dbSet.Find(entity.Id);  
  21.             dbContext.Entry(oldEntity).CurrentValues.SetValues(entity);  
  22.         }   
  23.     }  

調(diào)用代碼如下:

  1. db.Update<Member>(member);  
  2. int count = db.SaveChanges(); 

 針對不同的情景,將執(zhí)行不同的行為:

  • 情景一:上面代碼第11行執(zhí)行后entry.State將為EntityState.Modified,會直接退出此Update方法直接進(jìn)入SaveChanges的執(zhí)行。此情景執(zhí)行的sql語句為只更新變更的實體屬性。
  • 情景二:將正確執(zhí)行 try 代碼塊。此情景執(zhí)行的sql語句為更新全部實體屬性。
  • 情景三:在代碼執(zhí)行到第12行的Attach方法時將拋出 InvalidOperationException 異常,接著執(zhí)行 catch 代碼塊。此情景執(zhí)行的sql語句為只更新變更的實體屬性。 

三、按需更新(更新指定實體屬性)

需求分析

前面已經(jīng)有整體更新了,很多時候也都能做到只更新變化的實體屬性,為什么還要來個“按需更新”的需求呢?主要基于以下幾點理由:

  • 整體更新中獲取數(shù)據(jù)的變更是要把新值與原始值的屬性一一對比的,因而整體更新要從數(shù)據(jù)庫中獲取完整的實體數(shù)據(jù),以保證被更新的只有我們想要改變的實體屬性,這樣進(jìn)行整體更新時至少要從數(shù)據(jù)庫中查詢一次數(shù)據(jù)
  • 執(zhí)行的更新語句有可能是更新所有實體屬性的(如上的情景三),如果實體屬性很多,就容易造成計算資源的浪費(因為我們只需要更新其中的某幾個屬性值)。
  • 不能只更新指定的實體屬性,有了按需更新,我們可以非常方便的只更新指定的屬性,沒有指定的屬性即使值變化了也不更新

需求實現(xiàn)

按需更新,也就是知道要更新的實體屬性,比如用戶要修改密碼,就只是要把Password這個屬性的值變更為指定的新值,其他的最好是盡量不驚動。當(dāng)然,至少還是要知道要更新數(shù)據(jù)的主鍵的,否則,更新對象就不明了。下面就以設(shè)置密碼為例來說明問題。

要設(shè)置密碼,我構(gòu)造了一個空的Member類來裝載新密碼:

  1. Member member = new Member {Id = 1, Password = "NewPassword" + DateTime.Now.Second}; 

然后,我們想當(dāng)然的寫出了如下實現(xiàn)代碼:

  1. private static void Method06()  
  2. {  
  3.     Member member = new Member {Id = 1, Password = "NewPassword" + DateTime.Now.Second};  
  4.     using (var db = new DataContext())  
  5.     {  
  6.         DbEntityEntry<Member> entry = db.Entry(member);  
  7.         entry.State = EntityState.Unchanged;  
  8.         entry.Property("Password").IsModified = true;  
  9.         int count = db.SaveChanges();  
  10.         Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  11.  
  12.         Member newMember = db.Members.Single(m => m.Id == 1);  
  13.         Console.WriteLine("更新后:{0}。", newMember.Password);  
  14.     }  

然后,在執(zhí)行第9行SaveChanges的時候引發(fā)了如下異常:

  1. 捕捉到 System.Data.Entity.Validation.DbEntityValidationException  
  2.   HResult=-2146232032  
  3.   Message=對一個或多個實體的驗證失敗。有關(guān)詳細(xì)信息,請參見“EntityValidationErrors”屬性。  
  4.   Source=EntityFramework 
  5.   StackTrace:  
  6.        在 System.Data.Entity.Internal.InternalContext.SaveChanges()  
  7.        在 System.Data.Entity.Internal.LazyInternalContext.SaveChanges()  
  8.        在 System.Data.Entity.DbContext.SaveChanges()  
  9.        在 GmfEFUpdateDemo.Program.Method06() 位置 d:\Documents\Visual Studio 2012\Projects\GmfEFUpdateDemo\GmfEFUpdateDemo\Program.cs:行號 224  
  10.        在 GmfEFUpdateDemo.Program.Main(String[] args) 位置 d:\Documents\Visual Studio 2012\Projects\GmfEFUpdateDemo\GmfEFUpdateDemo\Program.cs:行號 63  
  11.   InnerException: 

為什么出現(xiàn)此異常?因為前面我們創(chuàng)建的Member對象只包含一個Id,一個Password屬性,其他的屬性并沒有賦值,也不考慮是否規(guī)范,這樣就定義出了一個不符合實體類驗證定義的對象了(Member類要求UserName屬性是不可為空的)。幸好,DbContext.Configuration中給我們定義了是否在保存時驗證實體有效性(ValidateOnSaveEnabled)這個開關(guān),我們只要在執(zhí)行按需更新的保存時把驗證閉,在保存成功后再開啟即可,更改代碼如下:

  1. private static void Method06()  
  2. {  
  3.     Member member = new Member {Id = 1, Password = "NewPassword" + DateTime.Now.Second};  
  4.     using (var db = new DataContext())  
  5.     {  
  6.         DbEntityEntry<Member> entry = db.Entry(member);  
  7.         entry.State = EntityState.Unchanged;  
  8.         entry.Property("Password").IsModified = true;  
  9.         db.Configuration.ValidateOnSaveEnabled = false;  
  10.         int count = db.SaveChanges();  
  11.         db.Configuration.ValidateOnSaveEnabled = true;  
  12.         Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  13.  
  14.         Member newMember = db.Members.Single(m => m.Id == 1);  
  15.         Console.WriteLine("更新后:{0}。", newMember.Password);  
  16.     }  

與整體更新一樣,理所當(dāng)然的會出現(xiàn)當(dāng)前上下文已經(jīng)存在了相同主鍵的實體數(shù)據(jù)的情況,當(dāng)然,根據(jù)之前的經(jīng)驗,也很容易的進(jìn)行處理了:

  1. private static void Method07()  
  2.     {  
  3.         Member member = new Member { Id = 1, Password = "NewPassword" + DateTime.Now.Second };  
  4.         using (var db = new DataContext())  
  5.         {  
  6.             //先查詢一次,讓上下文中存在相同主鍵的對象  
  7.             Member oldMember = db.Members.Single(m => m.Id == 1);  
  8.             Console.WriteLine("更新前:{0}。", oldMember.AddDate);  
  9.  
  10.             try 
  11.             {  
  12.                 DbEntityEntry<Member> entry = db.Entry(member);  
  13.                 entry.State = EntityState.Unchanged;  
  14.                 entry.Property("Password").IsModified = true;  
  15.             }  
  16.             catch (InvalidOperationException)  
  17.             {  
  18.                 DbEntityEntry<Member> entry = db.Entry(oldMember);  
  19.                 entry.CurrentValues.SetValues(member);  
  20.                 entry.State = EntityState.Unchanged;  
  21.                 entry.Property("Password").IsModified = true;  
  22.             }  
  23.             db.Configuration.ValidateOnSaveEnabled = false;  
  24.             int count = db.SaveChanges();  
  25.             db.Configuration.ValidateOnSaveEnabled = true;  
  26.             Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  27.  
  28.             Member newMember = db.Members.Single(m => m.Id == 1);  
  29.             Console.WriteLine("更新后:{0}。", newMember.Password);  
  30.         }  
  31.     } 

但是,上面的代碼卻無法正常工作,經(jīng)過調(diào)試發(fā)現(xiàn),當(dāng)執(zhí)行到第20行的時候,entry中跟蹤的數(shù)據(jù)又變回oldMember了,經(jīng)過一番EntityFramework源碼搜索,終于找到了問題的出處(System.Data.Entity.Internal.InternalEntityEntry類中):

  1. public EntityState State  
  2.     {  
  3.       get 
  4.       {  
  5.         if (!this.IsDetached)  
  6.           return this._stateEntry.State;  
  7.         else 
  8.           return EntityState.Detached;  
  9.       }  
  10.       set 
  11.       {  
  12.         if (!this.IsDetached)  
  13.         {  
  14.           if (this._stateEntry.State == EntityState.Modified && value == EntityState.Unchanged)  
  15.             this.CurrentValues.SetValues(this.OriginalValues);  
  16.           this._stateEntry.ChangeState(value);  
  17.         }  
  18.         else 
  19.         {  
  20.           switch (value)  
  21.           {  
  22.             case EntityState.Unchanged:  
  23.               this._internalContext.Set(this._entityType).InternalSet.Attach(this._entity);  
  24.               break;  
  25.             case EntityState.Added:  
  26.               this._internalContext.Set(this._entityType).InternalSet.Add(this._entity);  
  27.               break;  
  28.             case EntityState.Deleted:  
  29.             case EntityState.Modified:  
  30.               this._internalContext.Set(this._entityType).InternalSet.Attach(this._entity);  
  31.               this._stateEntry = this._internalContext.GetStateEntry(this._entity);  
  32.               this._stateEntry.ChangeState(value);  
  33.               break;  
  34.           }  
  35.         }  
  36.       }  
  37.     } 

第14、15行,當(dāng)狀態(tài)由Modified更改為Unchanged的時候,又把數(shù)據(jù)重新設(shè)置為舊的數(shù)據(jù)OriginalValues了。真吭!

好吧,看來在DbContext中折騰已經(jīng)沒戲了,只要去它老祖宗ObjectContext中找找出路,更改實現(xiàn)如下:

  1. private static void Method08()  
  2.     {  
  3.         Member member = new Member { Id = 1, Password = "NewPassword" + DateTime.Now.Second };  
  4.         using (var db = new DataContext())  
  5.         {  
  6.             //先查詢一次,讓上下文中存在相同主鍵的對象  
  7.             Member oldMember = db.Members.Single(m => m.Id == 1);  
  8.             Console.WriteLine("更新前:{0}。", oldMember.AddDate);  
  9.  
  10.             try 
  11.             {  
  12.                 DbEntityEntry<Member> entry = db.Entry(member);  
  13.                 entry.State = EntityState.Unchanged;  
  14.                 entry.Property("Password").IsModified = true;  
  15.             }  
  16.             catch (InvalidOperationException)  
  17.             {  
  18.                 ObjectContext objectContext = ((IObjectContextAdapter)db).ObjectContext;  
  19.                 ObjectStateEntry objectEntry = objectContext.ObjectStateManager.GetObjectStateEntry(oldMember);  
  20.                 objectEntry.ApplyCurrentValues(member);  
  21.                 objectEntry.ChangeState(EntityState.Unchanged);  
  22.                 objectEntry.SetModifiedProperty("Password");  
  23.             }  
  24.             db.Configuration.ValidateOnSaveEnabled = false;  
  25.             int count = db.SaveChanges();  
  26.             db.Configuration.ValidateOnSaveEnabled = true;  
  27.             Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  28.  
  29.             Member newMember = db.Members.Single(m => m.Id == 1);  
  30.             Console.WriteLine("更新后:{0}。", newMember.Password);  
  31.         }  
  32.     } 

catch代碼塊使用了EF4.0時代使用的ObjectContext來實現(xiàn),很好的達(dá)到了我們的目的,執(zhí)行的sql語句如下:

  1. exec sp_executesql N'update [dbo].[Members]  
  2. set [Password] = @0  
  3. where ([Id] = @1)  
  4. ',N'@0 nvarchar(50),@1 int',@0=N'NewPassword2',@1=1 

#p#

封裝重構(gòu)的分析

以上的實現(xiàn)中,屬性名都是以硬編碼的形式直接寫到實現(xiàn)類中,作為底層的封閉,這是肯定不行的,至少也要作為參數(shù)傳遞到一個通用的更新方法中。參照整體更新的擴(kuò)展方法定義,我們很容易的就能定義出如下簽名的擴(kuò)展方法:

  1. public static void Update<TEntity>(this DbContext dbContext, string[] propertyNames, params TEntity[] entities) where TEntity : EntityBase  
  2. 方法調(diào)用方式:  
  3. dbContext.Update<Member>(new[] {"Password"}, member); 

調(diào)用中屬性名依然要使用字符串的方式,寫起來麻煩,還容易出錯。看來,強(qiáng)類型才是最好的選擇。

寫到這,突然想起了做數(shù)據(jù)遷移的時候使用到的System.Data.Entity.Migrations.IDbSetExtensions 類中的擴(kuò)展方法

  1. public static void AddOrUpdate<TEntity>(this IDbSet<TEntity> set, Expression<Func<TEntity, object>> identifierExpression, params TEntity[] entities) where TEntity : class 

其中的參數(shù)Expression<Func<TEntity, object>> identifierExpression就是用于傳送實體屬性名的,于是,我們參照著,可以定義出如下簽名的更新方法:

  1. public static void Update<TEntity>(this DbContext dbContext, Expression<Func<TEntity, object>> propertyExpression, params TEntity[] entities) where TEntity : EntityBase  
  2.  
  3. 方法調(diào)用方式:  
  4. db.Update<Member>(m => new { m.Password }, member); 

到這里,如何從Expression<Func<TEntity, object>>獲得屬性名成為了完成封閉的關(guān)鍵。還是經(jīng)過調(diào)試,有了如下發(fā)現(xiàn):

運行時的Expression表達(dá)式中,Body屬性中有個類型為ReadOnlyCollection<MemberInfo> 的 Members集合屬性,我們需要的屬性正以MemberInfo的形式存在其中,因此,我們借助一下 dynamic 類型,將Members屬性解析出來,即可輕松得到我們想的數(shù)據(jù)。

  1. ReadOnlyCollection<MemberInfo> memberInfos = ((dynamic)propertyExpression.Body).Members; 

按需更新的最佳實現(xiàn)

經(jīng)過上面的分析,難點已逐個擊破,很輕松的就得到了如下擴(kuò)展方法的實現(xiàn):

  1. public static void Update<TEntity>(this DbContext dbContext, Expression<Func<TEntity, object>> propertyExpression, params TEntity[] entities)  
  2.         where TEntity : EntityBase  
  3.     {  
  4.         if (propertyExpression == nullthrow new ArgumentNullException("propertyExpression");  
  5.         if (entities == nullthrow new ArgumentNullException("entities");  
  6.         ReadOnlyCollection<MemberInfo> memberInfos = ((dynamic)propertyExpression.Body).Members;  
  7.         foreach (TEntity entity in entities)  
  8.         {  
  9.             try 
  10.             {  
  11.                 DbEntityEntry<TEntity> entry = dbContext.Entry(entity);  
  12.                 entry.State = EntityState.Unchanged;  
  13.                 foreach (var memberInfo in memberInfos)  
  14.                 {  
  15.                     entry.Property(memberInfo.Name).IsModified = true;  
  16.                 }  
  17.             }  
  18.             catch (InvalidOperationException)  
  19.             {  
  20.                 TEntity originalEntity = dbContext.Set<TEntity>().Local.Single(m => m.Id == entity.Id);  
  21.                 ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;  
  22.                 ObjectStateEntry objectEntry = objectContext.ObjectStateManager.GetObjectStateEntry(originalEntity);  
  23.                 objectEntry.ApplyCurrentValues(entity);  
  24.                 objectEntry.ChangeState(EntityState.Unchanged);  
  25.                 foreach (var memberInfo in memberInfos)  
  26.                 {  
  27.                     objectEntry.SetModifiedProperty(memberInfo.Name);  
  28.                 }  
  29.             }  
  30.         }  
  31.     } 

注意,這里的第20行雖然進(jìn)行了原始數(shù)據(jù)的查詢,但是從DbSet<T>.Local中進(jìn)行的查詢,而且前面的異常,也確定了Local中一定存在一個主鍵相同的原始數(shù)據(jù),所以敢用Single直接獲取。可以放心的是,這里并不會走數(shù)據(jù)庫查詢。

除此之外,還有一個可以封閉的地方就是關(guān)閉了ValidateOnSaveEnabled屬性的SaveChanges方法,可封閉為如下:

  1. public static int SaveChanges(this DbContext dbContext, bool validateOnSaveEnabled)  
  2.     {  
  3.         bool isReturn = dbContext.Configuration.ValidateOnSaveEnabled != validateOnSaveEnabled;  
  4.         try 
  5.         {  
  6.             dbContext.Configuration.ValidateOnSaveEnabled = validateOnSaveEnabled;  
  7.             return dbContext.SaveChanges();  
  8.         }  
  9.         finally 
  10.         {  
  11.             if (isReturn)  
  12.             {  
  13.                 dbContext.Configuration.ValidateOnSaveEnabled = !validateOnSaveEnabled;  
  14.             }  
  15.         }  
  16.     } 

辛苦不是白費的,經(jīng)過一番折騰,我們的按需更新實現(xiàn)起來就非常簡單了:

  1. private static void Method09()  
  2.     {  
  3.         Member member = new Member { Id = 1, Password = "NewPassword" + DateTime.Now.Second };  
  4.         using (var db = new DataContext())  
  5.         {  
  6.             //先查詢一次,讓上下文中存在相同主鍵的對象  
  7.             Member oldMember = db.Members.Single(m => m.Id == 1);  
  8.             Console.WriteLine("更新前:{0}。", oldMember.AddDate);  
  9.  
  10.             db.Update<Member>(m => new { m.Password }, member);  
  11.             int count = db.SaveChanges(false);  
  12.             Console.WriteLine("操作結(jié)果:{0}", count > 0 ? "更新成功。" : "未更新。");  
  13.  
  14.             Member newMember = db.Members.Single(m => m.Id == 1);  
  15.             Console.WriteLine("更新后:{0}。", newMember.Password);  
  16.         }  
  17.     } 

只需要第10,11行兩行代碼,即可完成完美的按需更新功能。

這里需要特別注意的是,此按需更新的方法只適用于使用新建上下文的環(huán)境中,即using(var db = DataContext()){ },因為我們往上下文中附加了一個非法的實體類(比如上面的member),當(dāng)提交更改之后,這個非法的實體類依然會存在于上下文中,如果使用這個上下文進(jìn)行后續(xù)的其他操作,將有可能出現(xiàn)異常。嘗試過在SaveChanges之后將該實體從上下文中移除,跟蹤系統(tǒng)會將該實體變更為刪除狀態(tài),在下次SaveChanges的時候?qū)⒅畡h除,這個問題本人暫時還沒有好的解決方案,在此特別說明。

四、源碼獲取

 

本文示例源碼下載:GmfEFUpdateDemo.zip

為了讓大家能第一時間獲取到本架構(gòu)的最新代碼,也為了方便我對代碼的管理,本系列的源碼已加入微軟的開源項目網(wǎng)站 http://www.codeplex.com,地址為:

https://gmframework.codeplex.com/

原文鏈接:http://www.cnblogs.com/guomingfeng/p/mvc-ef-update.html

責(zé)任編輯:林師授 來源: 博客園
相關(guān)推薦

2013-09-08 22:40:38

EF Code Fir數(shù)據(jù)查詢架構(gòu)設(shè)計

2013-09-08 22:12:02

EF Code Fir數(shù)據(jù)遷移MVC架構(gòu)設(shè)計

2013-09-08 21:41:10

RepositoryUnitOfWorkDbContext

2013-09-08 23:30:56

EF Code Fir架構(gòu)設(shè)計MVC架構(gòu)設(shè)計

2013-09-08 22:30:45

EF Code Fir架構(gòu)設(shè)計MVC架構(gòu)設(shè)計

2009-03-16 15:07:20

JSP分頁window.openJSP表單

2018-05-29 15:16:59

威脅防御

2011-08-18 11:05:21

jQuery

2023-07-21 01:12:30

Reactfalse?變量

2023-06-16 23:57:56

智能運營系統(tǒng)

2018-05-02 13:59:01

大數(shù)據(jù)數(shù)據(jù)收集數(shù)據(jù)科學(xué)

2021-07-20 15:37:37

數(shù)據(jù)開發(fā)大數(shù)據(jù)Spark

2012-08-09 09:10:56

代碼審查代碼

2024-08-21 08:02:47

2014-06-09 15:50:08

2011-12-21 13:35:39

JavaJFreeChart

2014-08-19 10:06:53

IAP

2013-12-04 09:35:02

云安全移動數(shù)據(jù)

2013-05-17 11:43:55

主數(shù)據(jù)數(shù)據(jù)管理

2017-10-20 08:25:10

數(shù)據(jù)收集工具數(shù)據(jù)源
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产精品99久久久久久www | 一级黄色毛片免费 | 国产黄色在线观看 | 99亚洲精品 | 国产国拍亚洲精品av | 精品成人佐山爱一区二区 | 久久久久国产精品午夜一区 | 日本精a在线观看 | 欧美性受| 免费毛片网站在线观看 | 亚洲国产日韩欧美 | 国产免费a | 国产美女精品视频 | 国内自拍视频在线观看 | 精品一区欧美 | 最新av在线网址 | 久久区二区| 精精国产xxxx视频在线播放 | 亚洲一二三区在线观看 | 亚洲第一天堂 | 国精日本亚洲欧州国产中文久久 | 久久这里只有精品首页 | 欧美国产视频 | 老司机67194精品线观看 | 亚洲欧美一区二区三区国产精品 | 女生羞羞网站 | 综合色在线 | 精品视频在线观看 | 国产精品久久午夜夜伦鲁鲁 | 夜夜爽99久久国产综合精品女不卡 | www.97国产 | 欧美日韩高清免费 | 精品视频一区二区 | 日本一区高清 | 久久久久久久久淑女av国产精品 | 欧美午夜一区二区三区免费大片 | 久久99久久| 欧美午夜精品久久久久久浪潮 | 日韩视频区 | 99九色| 久久福利 |