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

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

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

一、前言

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

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

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

  1. 部門(mén):一個(gè)部門(mén)可有多個(gè)角色【1-N】
  2. 角色:一個(gè)角色必有一個(gè)部門(mén)【N-1】,一個(gè)角色可有多個(gè)人員【N-N】
  3. 人員:一個(gè)人員可有多個(gè)角色【N-N】

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

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

  1. protected override void Seed(GmfEFUpdateDemo.Models.DataContext context)  
  2. {  
  3.     //部門(mén)  
  4.     var departments = new []  
  5.     {  
  6.         new Department {Name = "技術(shù)部"},  
  7.         new Department {Name = "財(cái)務(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 = "財(cái)務(wù)部經(jīng)理", Department = context.Departments.Single(m=>m.Name =="財(cái)務(wù)部")},  
  19.         new Role{Name = "會(huì)計(jì)", Department = context.Departments.Single(m=>m.Name =="財(cái)務(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ù)取出來(lái)更新后直接保存:

代碼:

  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語(yǔ)句如下:

  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 

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

情景二:從上下文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.     }  

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

  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中已存在與外來(lái)實(shí)體主鍵相同的數(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.         //先查詢(xún)一次,讓上下文中存在相同主鍵的對(duì)象  
  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三行代碼,目的是制造一個(gè)要更新的數(shù)據(jù)在上下文2中正在使用的場(chǎng)景,這時(shí)會(huì)發(fā)生什么情況呢?

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

  1. 捕捉到 System.InvalidOperationException  
  2.   HResult=-2146233079  
  3.   Message=ObjectStateManager 中已存在具有同一鍵的對(duì)象。ObjectStateManager 無(wú)法跟蹤具有相同鍵的多個(gè)對(duì)象。  
  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:行號(hào) 148  
  13.        在 GmfEFUpdateDemo.Program.Main(String[] args) 位置 d:\Documents\Visual Studio 2012\Projects\GmfEFUpdateDemo\GmfEFUpdateDemo\Program.cs:行號(hào) 54  
  14.   InnerException: 

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

 這應(yīng)該是一個(gè)非常常見(jiàn)的場(chǎng)景,也就是必須想辦法解決的場(chǎng)景。其實(shí)只要獲得現(xiàn)有實(shí)體數(shù)據(jù)的跟蹤,再把新數(shù)據(jù)賦到現(xiàn)有實(shí)體上,就可以解決問(wèn)題了,此方法唯一的缺點(diǎn)就是要獲取到舊的實(shí)體數(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.         //先查詢(xún)一次,讓上下文中存在相同主鍵的對(duì)象  
  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行是核心代碼,先從上下文中的舊實(shí)體獲取跟蹤,第19行的SetValues方法就是把新值設(shè)置到舊實(shí)體上(這一條很強(qiáng)大,支持任何類(lèi)型,比如ViewObject,DTO與POCO可以直接映射傳值)。由于值的更新是直接在上下文中的現(xiàn)有實(shí)體上進(jìn)行的,EF會(huì)自己跟蹤值的變化,因此這里并不需要我們來(lái)強(qiáng)制設(shè)置狀態(tài)為Modified,執(zhí)行的sql語(yǔ)句也足夠簡(jiǎn)單:

  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#

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

綜合上面的幾種情景,我們可以得到EF對(duì)實(shí)體整體更新的最佳方案,這里寫(xiě)成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(); 

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

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

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

需求分析

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

  • 整體更新中獲取數(shù)據(jù)的變更是要把新值與原始值的屬性一一對(duì)比的,因而整體更新要從數(shù)據(jù)庫(kù)中獲取完整的實(shí)體數(shù)據(jù),以保證被更新的只有我們想要改變的實(shí)體屬性,這樣進(jìn)行整體更新時(shí)至少要從數(shù)據(jù)庫(kù)中查詢(xún)一次數(shù)據(jù)
  • 執(zhí)行的更新語(yǔ)句有可能是更新所有實(shí)體屬性的(如上的情景三),如果實(shí)體屬性很多,就容易造成計(jì)算資源的浪費(fèi)(因?yàn)槲覀冎恍枰缕渲械哪硯讉€(gè)屬性值)。
  • 不能只更新指定的實(shí)體屬性,有了按需更新,我們可以非常方便的只更新指定的屬性,沒(méi)有指定的屬性即使值變化了也不更新

需求實(shí)現(xiàn)

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

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

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

然后,我們想當(dāng)然的寫(xiě)出了如下實(shí)現(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的時(shí)候引發(fā)了如下異常:

  1. 捕捉到 System.Data.Entity.Validation.DbEntityValidationException  
  2.   HResult=-2146232032  
  3.   Message=對(duì)一個(gè)或多個(gè)實(shí)體的驗(yàn)證失敗。有關(guān)詳細(xì)信息,請(qǐng)參見(jiàn)“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:行號(hào) 224  
  10.        在 GmfEFUpdateDemo.Program.Main(String[] args) 位置 d:\Documents\Visual Studio 2012\Projects\GmfEFUpdateDemo\GmfEFUpdateDemo\Program.cs:行號(hào) 63  
  11.   InnerException: 

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

  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)然的會(huì)出現(xiàn)當(dāng)前上下文已經(jīng)存在了相同主鍵的實(shí)體數(shù)據(jù)的情況,當(dāng)然,根據(jù)之前的經(jīng)驗(yàn),也很容易的進(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.             //先查詢(xún)一次,讓上下文中存在相同主鍵的對(duì)象  
  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.     } 

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

  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í)候,又把數(shù)據(jù)重新設(shè)置為舊的數(shù)據(jù)OriginalValues了。真吭!

好吧,看來(lái)在DbContext中折騰已經(jīng)沒(méi)戲了,只要去它老祖宗ObjectContext中找找出路,更改實(shí)現(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.             //先查詢(xún)一次,讓上下文中存在相同主鍵的對(duì)象  
  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時(shí)代使用的ObjectContext來(lái)實(shí)現(xiàn),很好的達(dá)到了我們的目的,執(zhí)行的sql語(yǔ)句如下:

  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)的分析

以上的實(shí)現(xiàn)中,屬性名都是以硬編碼的形式直接寫(xiě)到實(shí)現(xiàn)類(lèi)中,作為底層的封閉,這是肯定不行的,至少也要作為參數(shù)傳遞到一個(gè)通用的更新方法中。參照整體更新的擴(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)用中屬性名依然要使用字符串的方式,寫(xiě)起來(lái)麻煩,還容易出錯(cuò)。看來(lái),強(qiáng)類(lèi)型才是最好的選擇。

寫(xiě)到這,突然想起了做數(shù)據(jù)遷移的時(shí)候使用到的System.Data.Entity.Migrations.IDbSetExtensions 類(lèi)中的擴(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就是用于傳送實(shí)體屬性名的,于是,我們參照著,可以定義出如下簽名的更新方法:

  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)過(guò)調(diào)試,有了如下發(fā)現(xiàn):

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

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

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

經(jīng)過(guò)上面的分析,難點(diǎn)已逐個(gè)擊破,很輕松的就得到了如下擴(kuò)展方法的實(shí)現(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ù)的查詢(xún),但是從DbSet<T>.Local中進(jìn)行的查詢(xún),而且前面的異常,也確定了Local中一定存在一個(gè)主鍵相同的原始數(shù)據(jù),所以敢用Single直接獲取??梢苑判牡氖牵@里并不會(huì)走數(shù)據(jù)庫(kù)查詢(xún)。

除此之外,還有一個(gè)可以封閉的地方就是關(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.     } 

辛苦不是白費(fèi)的,經(jīng)過(guò)一番折騰,我們的按需更新實(shí)現(xiàn)起來(lái)就非常簡(jiǎ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.             //先查詢(xún)一次,讓上下文中存在相同主鍵的對(duì)象  
  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()){ },因?yàn)槲覀兺舷挛闹懈郊恿艘粋€(gè)非法的實(shí)體類(lèi)(比如上面的member),當(dāng)提交更改之后,這個(gè)非法的實(shí)體類(lèi)依然會(huì)存在于上下文中,如果使用這個(gè)上下文進(jìn)行后續(xù)的其他操作,將有可能出現(xiàn)異常。嘗試過(guò)在SaveChanges之后將該實(shí)體從上下文中移除,跟蹤系統(tǒng)會(huì)將該實(shí)體變更為刪除狀態(tài),在下次SaveChanges的時(shí)候?qū)⒅畡h除,這個(gè)問(wèn)題本人暫時(shí)還沒(méi)有好的解決方案,在此特別說(shuō)明。

四、源碼獲取

 

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

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

https://gmframework.codeplex.com/

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

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

2013-09-08 22:40:38

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

2013-09-08 22:12:02

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

2013-09-08 21:41:10

RepositoryUnitOfWorkDbContext

2013-09-08 23:30:56

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

2013-09-08 22:30:45

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

2009-03-16 15:07:20

JSP分頁(yè)window.openJSP表單

2023-07-21 01:12:30

Reactfalse?變量

2011-08-18 11:05:21

jQuery

2018-05-29 15:16:59

威脅防御

2023-06-16 23:57:56

智能運(yùn)營(yíng)系統(tǒng)

2018-05-02 13:59:01

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

2021-07-20 15:37:37

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

2011-12-21 13:35:39

JavaJFreeChart

2014-08-19 10:06:53

IAP

2024-08-21 08:02:47

2012-08-09 09:10:56

代碼審查代碼

2014-06-09 15:50:08

2015-09-23 09:08:38

java反射

2011-12-21 13:52:27

JavaJFreeChart

2023-09-11 08:50:03

Maven工具關(guān)系管理
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 亚洲一区二区视频在线播放 | 一区二区在线不卡 | 欧美一区二区三区久久精品 | 成人一区精品 | 成人国产午夜在线观看 | 国产欧美日韩精品一区 | 天堂久久网 | 亚洲一区二区三区四区在线观看 | 99精品国产一区二区三区 | 999久久久久久久久6666 | 特黄一级 | 毛片一级网站 | 亚洲日本一区二区 | 久久久久国产一区二区三区 | 国产激情精品 | 中文二区 | 精品国产一区二区国模嫣然 | 久久里面有精品 | 精品国产精品一区二区夜夜嗨 | www狠狠爱com| 97av | 久草免费在线视频 | 亚洲国产成人精品久久 | 3p视频在线观看 | 精品成人免费一区二区在线播放 | 天天操天天射综合网 | www.亚洲区| 国产日韩视频在线 | av成人在线观看 | 三极网站 | 久久国品片 | 亚洲 91 | 亚洲视频二| 日日干夜夜操 | 日韩在线免费播放 | 911网站大全在线观看 | 天天干狠狠操 | 亚洲成人日韩 | 成人午夜av | 国产日韩精品在线 | 欧美一区二区三区视频在线 |