日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态...

發布時間:2023/11/30 数据库 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
EF里查看/修改實體的當前值、原始值和數據庫值以及重寫SaveChanges方法記錄實體狀態 原文:EF里查看/修改實體的當前值、原始值和數據庫值以及重寫SaveChanges方法記錄實體狀態

本文目錄

  • 查看實體當前、原始和數據庫值:DbEntityEntry
  • 查看實體的某個屬性值:GetValue<TValue>方法
  • 拷貝DbPropertyValues到實體:ToObject方法
  • 修改DbPropertyValues當前值:索引器
  • 克隆實體:Clone方法
  • 設置實體的值:SetValues方法
  • 克隆實體:SetValues
  • 獲取和設置實體的單個屬性:Property方法
  • 查詢實體的屬性是否被修改:IsModified方法
  • 修改導航屬性
  • 重新加載實體:Reload方法
  • 讀取相關聯的實體和狀態:DbContext.ChangeTracker.Entries方法
  • EF里如何解決更新時的沖突
  • 重寫上下文的SaveChanges方法記錄結果集里實體的各種增/刪/改
  • 本文源碼和系列文章導航

文章開始前建議大家為了更好的記憶最好自己實現文中的所有方法。如果非要直接運行我的demo,必要的時候需要恢復下數據庫數據,否則找不到記錄。

之前的章節已經演示了context.Entry方法可以拿到實體的狀態(EntityState),來看一個方法:

/// <summary>/// 單個實體的狀態/// </summary>private static void PrintState(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var canyon = (from d in context.Destinationswhere d.Name == "Grand Canyon"select d).Single();DbEntityEntry<DbContexts.Model.Destination> entry = context.Entry(canyon);Console.WriteLine("Before Edit:{0}", entry.State); //Unchaged canyon.TravelWarnings = "Take a lot of Water!";DbEntityEntry<DbContexts.Model.Destination> entrys = context.Entry(canyon);
Console.WriteLine(
"After Edit:{0}", entrys.State); //Modified }}

context.Entry方法有兩個重載,分別返回泛型DbEntityEntry<TEntity>和非泛型的DbEntityEntry,它們都可以監測到實體的狀態,并且通過DbEntityEntry還可以操作實體的當前值、原始值和數據庫值。分別是:

  • 當前值(Current Value):程序里設置實體屬性的值(在內存中,還沒提交數據庫);
  • 原始值(Original Value):被數據庫上下文跟蹤到時的值(程序取出數據庫的值,可能不是最新的);
  • 數據庫值(Database Value):數據庫里的值(此時此刻數據庫里最新的值)

來看一個例子:

/// <summary>/// 打印實體當前、原始和數據庫值/// </summary>private static void PrintLodgingInfo(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var hotel = (from d in context.Lodgingswhere d.Name == "Grand Hotel"select d).Single();hotel.Name = "Super Grand Hotel";context.Database.ExecuteSqlCommand(@"UPDATE Lodgings SET Name = 'Not-So-Grand Hotel' WHERE Name = 'Grand Hotel'");PrintChangeTrackingInfo(context, hotel);}}private static void PrintChangeTrackingInfo(DbContexts.DataAccess.BreakAwayContext context, DbContexts.Model.Lodging entity){var entry = context.Entry(entity);Console.WriteLine(entry.Entity.Name);Console.WriteLine("State: {0}", entry.State);Console.WriteLine("\nCurrent Values:");PrintPropertyValues(entry.CurrentValues);Console.WriteLine("\nOriginal Values:");PrintPropertyValues(entry.OriginalValues);Console.WriteLine("\nDatabase Values:");PrintPropertyValues(entry.GetDatabaseValues());}private static void PrintPropertyValues(DbPropertyValues values){foreach (var propertyName in values.PropertyNames){Console.WriteLine(" - {0}: {1}", propertyName, values[propertyName]);}}

方法分析:先從數據庫取出一個實體,然后修改其Name屬性,這個時候當前值(Current)和原始值(Original)都有了,分別是:修改后的值(還沒提交,在內存中)和從庫里取出來時實體的值。再使用Database.ExecuteSqlCommand執行了一段修改此對象在數據庫中的值,這個時候數據庫值(Database)也有了變化,這個實體的三個值都不相同了。還沒看到打印結果,在執行entry.GetDatabaseValues()方法時報了一個EntitySqlException錯:

找不到類型DbContexts.DataAccess.Lodging,項目的Lodging實體明明在DbContexts.Model.Lodging命名空間下,反復檢查代碼沒發現任何問題,報這個錯真是很疑惑。最后通過搜索引擎才知道這是EF4.1/4.2版本的一個bug,解決辦法:修改實體和上下文到一個命名空間,或者使用EF4.3 release。看看本書作者Julie Lerman在msdn論壇上關于此bug的回復

換成4.3版本的EF問題就立馬解決了(源碼的libs目錄下提供了EF4.3)。看下打印的結果:

結果分析:當前值為方法里修改的值、原始值是從數據庫取出未做任何操作的值、數據庫值是此時數據庫里的值。當然新添加的實體不會有原始值和數據庫值、刪除的實體也不會有當前值,利用EntityState完善下方法:

private static void PrintChangeTrackingInfo(DbContexts.DataAccess.BreakAwayContext context, DbContexts.Model.Lodging entity){var entry = context.Entry(entity);Console.WriteLine(entry.Entity.Name);Console.WriteLine("State: {0}", entry.State);if (entry.State != EntityState.Deleted) //標記刪除的實體不會有當前值 {Console.WriteLine("\nCurrent Values:");PrintPropertyValues(entry.CurrentValues);}if (entry.State != EntityState.Added) //新添加的時候不會有原始值和數據庫值 {Console.WriteLine("\nOriginal Values:");PrintPropertyValues(entry.OriginalValues);Console.WriteLine("\nDatabase Values:");PrintPropertyValues(entry.GetDatabaseValues());}}

為了測試重寫下PrintLodgingInfo方法:

/// <summary>/// 測試打印添加和刪除時實體當前、原始和數據庫值/// </summary>private static void PrintLodgingInfoAddAndDelete(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var hotel = (from d in context.Lodgingswhere d.Name == "Grand Hotel"select d).Single();PrintChangeTrackingInfo(context, hotel); //默認var davesDump = (from d in context.Lodgingswhere d.Name == "Dave's Dump"select d).Single();context.Lodgings.Remove(davesDump);PrintChangeTrackingInfo(context, davesDump); //測試刪除實體var newMotel = new DbContexts.Model.Lodging { Name = "New Motel" };context.Lodgings.Add(newMotel);PrintChangeTrackingInfo(context, newMotel); //測試新添加實體 }}

當然上面打印實體類型的方法并不通用,修改第二個參數為object類型:

/// <summary>/// 通用的打印實體方法/// </summary>private static void PrintChangeTrackingInfo(DbContexts.DataAccess.BreakAwayContext context, object entity){var entry = context.Entry(entity);Console.WriteLine("Type:{0}", entry.Entity.GetType()); //打印實體類型Console.WriteLine("State: {0}", entry.State);if (entry.State != EntityState.Deleted) //標記刪除的實體不會有當前值 {Console.WriteLine("\nCurrent Values:");PrintPropertyValues(entry.CurrentValues);}if (entry.State != EntityState.Added) //新添加的時候不會有原始值和數據庫值 {Console.WriteLine("\nOriginal Values:");PrintPropertyValues(entry.OriginalValues);Console.WriteLine("\nDatabase Values:");PrintPropertyValues(entry.GetDatabaseValues());}}

看看打印結果:

之前打印實體的各種屬性都是通過遍歷的形式(PrintPropertyValues方法)打印出來,如果僅取某個字段當然沒必要這么麻煩,可以使用GetValue<TValue>:

/// <summary>/// 打印實體單個屬性/// </summary>private static void PrintOriginalName(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var hotel = (from d in context.Lodgingswhere d.Name == "Grand Hotel"select d).Single();hotel.Name = "Super Grand Hotel";string originalName = context.Entry(hotel).OriginalValues.GetValue<string>("Name");Console.WriteLine("Current Name: {0}", hotel.Name); //Super Grand HotelConsole.WriteLine("Original Name: {0}", originalName); //Grand Hotel}}

拷貝DbPropertyValues到實體:ToObject方法

/// <summary>/// 拷貝DbPropertyValues到實體:ToObject方法/// </summary>private static void TestPrintDestination(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var reef = (from d in context.Destinationswhere d.Name == "Great Barrier Reef"select d).Single();reef.TravelWarnings = "Watch out for sharks!";Console.WriteLine("Current Values");PrintDestination(reef);Console.WriteLine("\nDatabase Values");DbPropertyValues dbValues = context.Entry(reef).GetDatabaseValues();PrintDestination((DbContexts.Model.Destination)dbValues.ToObject()); //ToObject方法創建Destination實例 }}private static void PrintDestination(DbContexts.Model.Destination destination){Console.WriteLine("-- {0}, {1} --", destination.Name, destination.Country);Console.WriteLine(destination.Description);if (destination.TravelWarnings != null){Console.WriteLine("WARNINGS!: {0}", destination.TravelWarnings);}}

方法分析:從Destination表里取出Name為Great Barrier Reef的實體并修改其TravelWarnings字段,然后調用PrintDestination方法打印當前實體的各屬性,再查出此實體在數據庫里的值,并且通過ToObject方法把數據庫取出來的這個對象也轉換成了實體對象。這么轉有什么好處呢?這個通過ToObject轉換的Destination實例不會被數據庫上下文追蹤,所以對其做的任何改變都不會提交數據庫。看看打印結果:

修改DbPropertyValues當前值:

調用上下文的Entry方法,傳入要操作的實體對象,再打點就可以拿到實體的當前值(CurrentValues)、原始值(OriginalValues)、數據庫值(GetDatabaseValues()),返回類型是DbPropertyValues,直接遍歷就可以輸出實體的所有屬性。當然DbPropertyValues并不是只讀的。寫個方法修改試試:

/// <summary>/// 修改DbPropertyValues當前值/// </summary>private static void ChangeCurrentValue(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var hotel = (from d in context.Lodgingswhere d.Name == "Grand Hotel"select d).Single();context.Entry(hotel).CurrentValues["Name"] = "Hotel Pretentious";Console.WriteLine("Property Value: {0}", hotel.Name);Console.WriteLine("State: {0}", context.Entry(hotel).State); //Modified }}

類似于索引器的方式賦值即可,賦值后實體的狀態已經是Modified了,顯然已經被上下文追蹤到了,這個時候調用上下文的SaveChanges方法將會提交到數據庫。那么如果只是想打印和修改實體狀態以供查看,并不像被提交到數據庫怎么辦?


最好的辦法就是克隆,先克隆實體然后操作克隆之后的實體:

/// <summary>/// 克隆實體:Clone/// </summary>private static void CloneCurrentValues(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var hotel = (from d in context.Lodgingswhere d.Name == "Grand Hotel"select d).Single();var values = context.Entry(hotel).CurrentValues.Clone(); //Clone方法values["Name"] = "Simple Hotel";Console.WriteLine("Property Value: {0}", hotel.Name);Console.WriteLine("State: {0}", context.Entry(hotel).State); //Unchanged }}

設置實體的值:SetValues方法

當然實體的當前值、原始值和數據庫值都是可以相互復制的:

/// <summary>/// 設置實體的值:SetValues方法/// </summary>private static void UndoEdits(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var canyon = (from d in context.Destinationswhere d.Name == "Grand Canyon"select d).Single();canyon.Name = "Bigger & Better Canyon";var entry = context.Entry(canyon);entry.CurrentValues.SetValues(entry.OriginalValues);entry.State = EntityState.Unchanged; //標記未修改 Console.WriteLine("Name: {0}", canyon.Name); //Grand Canyon}}

上面的方法演示了拷貝原始值到當前值,最終保存的是當前值。很方便,不需要挨個賦值。

再看看如何使用SetValues方法實現之前說的克隆實體:

/// <summary>/// 克隆實體:SetValues/// </summary>private static void CreateDavesCampsite(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var davesDump = (from d in context.Lodgingswhere d.Name == "Dave's Dump"select d).Single();var clone = new DbContexts.Model.Lodging();context.Lodgings.Add(clone);context.Entry(clone).CurrentValues.SetValues(davesDump); //克隆davesDump的值到新對象clone里clone.Name = "Dave's Camp"; //修改Name屬性context.SaveChanges(); //最后提交修改 Console.WriteLine("Name: {0}", clone.Name); //Dave's CampConsole.WriteLine("Miles: {0}", clone.MilesFromNearestAirport); //32.65Console.WriteLine("Contact Id: {0}", clone.PrimaryContactId); //1}} exec sp_executesql N'insert [dbo].[Lodgings]([Name], [Owner], [MilesFromNearestAirport], [destination_id], [PrimaryContactId], [SecondaryContactId], [Entertainment], [Activities], [MaxPersonsPerRoom], [PrivateRoomsAvailable], [Discriminator]) values (@0, null, @1, @2, @3, null, null, null, null, null, @4) select [LodgingId] from [dbo].[Lodgings] where @@ROWCOUNT > 0 and [LodgingId] = scope_identity()',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 int,@4 nvarchar(128)',@0=N'Dave''s Camp',@1=32.65,@2=1,@3=1,@4=N'Lodging'

很明顯實體已經被克隆了。

獲取和設置實體的單個屬性:Property方法

/// <summary>/// 獲取和設置實體的單個屬性:Property方法/// </summary>private static void WorkingWithPropertyMethod(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var davesDump = (from d in context.Lodgingswhere d.Name == "Dave's Dump"select d).Single();var entry = context.Entry(davesDump);entry.Property(d => d.Name).CurrentValue = "Dave's Bargain Bungalows"; //設置Name屬性 Console.WriteLine("Current Value: {0}", entry.Property(d => d.Name).CurrentValue); //Dave's Bargain BungalowsConsole.WriteLine("Original Value: {0}", entry.Property(d => d.Name).OriginalValue); //Dave's DumpConsole.WriteLine("Modified?: {0}", entry.Property(d => d.Name).IsModified); //True }}

同樣可以查詢出實體的哪些屬性被修改了:IsModified方法

/// <summary>/// 查詢實體被修改字段:IsModified方法/// </summary>private static void FindModifiedProperties(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var canyon = (from d in context.Destinationswhere d.Name == "Grand Canyon"select d).Single();canyon.Name = "Super-Size Canyon";canyon.TravelWarnings = "Bigger than your brain can handle!!!";var entry = context.Entry(canyon);var propertyNames = entry.CurrentValues.PropertyNames; //獲取所有的Name列 IEnumerable<string> modifiedProperties = from name in propertyNameswhere entry.Property(name).IsModifiedselect name;foreach (var propertyName in modifiedProperties){Console.WriteLine(propertyName); //Name、TravelWarnings }}}

前面的章節已經講解了如何查詢一對一、一對多等關系的導航屬性了,還不了解的點這里。現在講講如何修改導航屬性:

/// <summary>/// 修改導航屬性(Reference):CurrentValue方法/// </summary>private static void WorkingWithReferenceMethod(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var davesDump = (from d in context.Lodgingswhere d.Name == "Dave's Dump"select d).Single();var entry = context.Entry(davesDump);entry.Reference(l => l.Destination).Load(); //顯示加載var canyon = davesDump.Destination;Console.WriteLine("Current Value After Load: {0}", entry.Reference(d => d.Destination).CurrentValue.Name);var reef = (from d in context.Destinationswhere d.Name == "Great Barrier Reef"select d).Single();entry.Reference(d => d.Destination).CurrentValue = reef; //修改Console.WriteLine("Current Value After Change: {0}", davesDump.Destination.Name);}}

打印結果:
Current Value After Load: Grand Canyon
Current Value After Change: Great Barrier Reef

注:上面的方法并沒有調用上下文的SaveChanges方法,故程序跑完數據也不會保存到數據庫,本文所有方法僅作演示都未提交數據庫。

有Reference找單個屬性的,那么自然也有Collection找集合屬性的:

/// <summary>/// 修改導航屬性(Collection):CurrentValue方法/// </summary>private static void WorkingWithCollectionMethod(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var res = (from r in context.Reservationswhere r.Trip.Description == "Trip from the database"select r).Single();var entry = context.Entry(res);entry.Collection(r => r.Payments).Load();Console.WriteLine("Payments Before Add: {0}", entry.Collection(r => r.Payments).CurrentValue.Count);var payment = new DbContexts.Model.Payment { Amount = 245 };context.Payments.Add(payment);entry.Collection(r => r.Payments).CurrentValue.Add(payment); //修改Console.WriteLine("Payments After Add: {0}", entry.Collection(r => r.Payments).CurrentValue.Count);}}

打印結果:
Payments Before Add: 1
Payments After Add: 2

從數據庫取出實體加載到內存中,可能并不立馬就展示給用戶看。在進行一系列的排序、篩選等操作再展示出來。但是怎么確定展示的時候這些實體沒有被修改過呢?可以使用Reload方法重新加載:

/// <summary>/// 取當前最新的數據庫值:Reload方法/// </summary>private static void ReloadLodging(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var hotel = (from d in context.Lodgingswhere d.Name == "Grand Hotel"select d).Single(); //取出實體context.Database.ExecuteSqlCommand(@"UPDATE dbo.Lodgings SET Name = 'Le Grand Hotel' WHERE Name = 'Grand Hotel'"); //立馬修改實體值(這個時候數據庫中的值已改變,但是取出來放在內存中的值并沒改變)Console.WriteLine("Name Before Reload: {0}", hotel.Name);Console.WriteLine("State Before Reload: {0}", context.Entry(hotel).State);context.Entry(hotel).Reload();Console.WriteLine("Name After Reload: {0}", hotel.Name);Console.WriteLine("State After Reload: {0}", context.Entry(hotel).State);}}

打印結果:
Name Before Reload: Grand Hotel
State Before Reload: Unchanged
Name After Reload: Le Grand Hotel
State After Reload: Unchanged

可以看出Reload方法已經重新取出了數據庫中的最新值。來看看Reload方法生成的sql:

SELECT [Extent1].[Discriminator] AS [Discriminator], [Extent1].[LodgingId] AS [LodgingId], [Extent1].[Name] AS [Name], [Extent1].[Owner] AS [Owner], [Extent1].[MilesFromNearestAirport] AS [MilesFromNearestAirport], [Extent1].[destination_id] AS [destination_id], [Extent1].[PrimaryContactId] AS [PrimaryContactId], [Extent1].[SecondaryContactId] AS [SecondaryContactId], [Extent1].[Entertainment] AS [Entertainment], [Extent1].[Activities] AS [Activities], [Extent1].[MaxPersonsPerRoom] AS [MaxPersonsPerRoom], [Extent1].[PrivateRoomsAvailable] AS [PrivateRoomsAvailable] FROM [dbo].[Lodgings] AS [Extent1] WHERE ([Extent1].[Discriminator] IN ('Resort','Hostel','Lodging')) AND ([Extent1].[LodgingId] = 1)

當然Reload方法也會保存內存中修改的數據,這個并不會沖突。在方法里的linq查詢后面加上:hotel.Name = "A New Name"; 打印結果就是這樣的了:
Name Before Reload: A New Name
State Before Reload: Modified
Name After Reload: Le Grand Hotel
State After Reload: Unchanged

注意,代碼里修改的Name已經顯示了,并且標記實體狀態為Modified了,Modified會在調用上下文的SaveChanges方法的時候提交到數據庫。這個過程是這樣的:

加載實體到內存中 - 在內存中對實體的某個屬性進行修改 - 使用ExecuteSqlCommand方法執行sql修改數據庫里該實體的值 - 調用Reload取出數據庫里本實體的最新值 - 調用SaveChanges方法的話,在內存中對實體的修改也會被提交到數據庫

之前操作了單個實體,現在看看如何讀取關聯實體和狀態。使用DbContext.ChangeTracker.Entries方法:

     /// <summary>/// 讀取相關聯的實體和狀態:DbContext.ChangeTracker.Entries方法/// </summary>private static void PrintChangeTrackerEntries(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var res = (from r in context.Reservationswhere r.Trip.Description == "Trip from the database"select r).Single();context.Entry(res).Collection(r => r.Payments).Load();
res.Payments.Add(
new DbContexts.Model.Payment { Amount = 245 });var entries = context.ChangeTracker.Entries();foreach (var entry in entries){Console.WriteLine("Entity Type: {0}", entry.Entity.GetType());Console.WriteLine(" - State: {0}", entry.State);}}}

添加了一個從表實體,并讀取所有關聯實體和其狀態,打印結果:
Entity Type: DbContexts.Model.Payment - State: Added
Entity Type: DbContexts.Model.Reservation - State: Unchanged
Entity Type: DbContexts.Model.Payment - State: Unchanged

EF里如何解決更新數據時的沖突

正常根據實體的主鍵修改實體的時候,EF是不會判斷數據修改之前有沒有被別的人修改過,但是如果做了并發控制,EF在更新某條記錄的時候才會拋錯。這個系列文章的demo里有兩個實體做了并發控制:Person類的SocialSecurityNumber字段被標記了ConcurrencyCheck;Trip類的RowVersion字段被標記了Timestamp。來寫一個觸發DbUpdateConcurrencyException異常的方法并處理這個異常:

/// <summary>/// 修改實體/// </summary>private static void ConcurrencyDemo(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var trip = (from t in context.Trip.Include(t => t.Destination)where t.Description == "Trip from the database"select t).Single();trip.Description = "Getaway in Vermont";context.Database.ExecuteSqlCommand(@"UPDATE dbo.Trips SET CostUSD = 400 WHERE Description = 'Trip from the database'");SaveWithConcurrencyResolution(context);}}/// <summary>/// 嘗試保存/// </summary>private static void SaveWithConcurrencyResolution(DbContexts.DataAccess.BreakAwayContext context){try{context.SaveChanges();}catch (DbUpdateConcurrencyException ex){ResolveConcurrencyConflicts(ex);SaveWithConcurrencyResolution(context);}}

方法分析:取出實體 - 修改實體Description屬性(此時實體狀態為Modified)- 使用ExecuteSqlCommand執行sql修改了CostUSD和Description字段(修改后時間戳已經不同了,PS:使用ExecuteSqlCommand執行sql不需要調用SaveChanges方法)- 調用上下文的SaveChanges方法保存之前被標記為Modified的實體,這個時候就會報一個DbUpdateConcurrencyException的異常,因為時間戳列已經找不到了,這個更新的where條件根本找不到記錄了。有時間戳的列更新都是雙條件,時間戳詳細用法點這里了解。

嘗試寫個方法解決這個沖突:

/// <summary>/// 解決沖突/// </summary>private static void ResolveConcurrencyConflicts(DbUpdateConcurrencyException ex){foreach (var entry in ex.Entries){Console.WriteLine("Concurrency conflict found for {0}", entry.Entity.GetType());Console.WriteLine("\nYou are trying to save the following values:");PrintPropertyValues(entry.CurrentValues); //用戶修改的值 Console.WriteLine("\nThe values before you started editing were:");PrintPropertyValues(entry.OriginalValues); //從庫里取出來時的值var databaseValues = entry.GetDatabaseValues(); //即時數據庫的值Console.WriteLine("\nAnother user has saved the following values:");PrintPropertyValues(databaseValues);Console.WriteLine("[S]ave your values, [D]iscard you changes or [M]erge?");var action = Console.ReadKey().KeyChar.ToString().ToUpper(); //讀取用戶輸入的字母switch (action){case "S":entry.OriginalValues.SetValues(databaseValues); //拷貝數據庫值到當前值(恢復時間戳)break;case "D":entry.Reload(); //重新加載break;case "M":var mergedValues = MergeValues(entry.OriginalValues, entry.CurrentValues, databaseValues);//合并entry.OriginalValues.SetValues(databaseValues); //拷貝數據庫值到當前值(恢復時間戳)entry.CurrentValues.SetValues(mergedValues); //拷貝合并后的值到當前值,最終保存的是當前值break;default:throw new ArgumentException("Invalid option");}}}

捕獲到異常后告知用戶要修改實體的原始值(用戶修改前從數據庫取出來的值)、現在的值(用戶修改的值)、數據庫里的值(此時數據庫里的值,這個值已被修改,不是用戶修改前取出來的值了),打印出來的結果顯示已經有人修改了這條記錄了。最后是問用戶是否保存修改。分別是保存、放棄、合并修改。

用戶輸入"S"表示“保存”,case語句塊里執行的操作是拷貝數據庫值到原始值,這里該有疑惑了,調用SaveChanges方法保存的也是currentValues當前值,跟databaseValues數據庫值還有OriginalValues原始值沒有任何關系啊。其實這么操作是恢復一下時間戳的值方便更新,之前說過timestamp的列更新條件是兩個,任何一個不對都更新不了。看看sql:

exec sp_executesql N'update [dbo].[Trips] set [Description] = @0, [CostUSD] = @1 where (([Identifier] = @2) and ([RowVersion] = @3)) select [RowVersion] from [dbo].[Trips] where @@ROWCOUNT > 0 and [Identifier] = @2',N'@0 nvarchar(max) ,@1 decimal(18,2),@2 uniqueidentifier,@3 binary(8)',@0=N'Getaway in Vermont',@1=1000.00,@2='CF2E6BD3-7393-440C-941A- 9124C61CE04A',@3=0x00000000000007D2

結果只保存了自己的修改:

用戶輸入“D”表示“放棄”,case語句塊里執行的是Reload方法,這個方法之前已經介紹過了,是重新加載數據庫里的最新值(Latest Value)。恢復下數據庫數據再執行下方法,看看sql:

SELECT [Extent1].[Identifier] AS [Identifier], [Extent1].[StartDate] AS [StartDate], [Extent1].[EndDate] AS [EndDate], [Extent1].[Description] AS [Description], [Extent1].[CostUSD] AS [CostUSD], [Extent1].[RowVersion] AS [RowVersion], [Extent1].[DestinationId] AS [DestinationId] FROM [dbo].[Trips] AS [Extent1] WHERE [Extent1].[Identifier] = cast('cf2e6bd3-7393-440c-941a-9124c61ce04a' as uniqueidentifier)

取了下數據庫里該實體最新的值(使用ExecuteSqlCommand更新后的值),沒有其他任何更新語句,就是放棄本次修改的意思,但是之前ExecuteSqlCommand方法執行的修改是有效的,看看結果:

上面的“保存修改”和“放棄修改”只能保存一個,如果讓用戶修改的和ExecuteSqlCommand的修改同時生效呢,選擇M,意為合并。看看合并方法:

/// <summary>/// 合并/// </summary>private static DbPropertyValues MergeValues(DbPropertyValues original, DbPropertyValues current, DbPropertyValues database){var result = original.Clone(); //拷貝原始值并存放合并后的值foreach (var propertyName in original.PropertyNames) //遍歷原始值的所有列 {if (original[propertyName] is DbPropertyValues) //判斷當前列是否復雜類型(很少) {var mergedComplexValues =MergeValues((DbPropertyValues)original[propertyName],(DbPropertyValues)current[propertyName],(DbPropertyValues)database[propertyName]); //是復雜類型的話就使用遞歸合并復雜類型的值((DbPropertyValues)result[propertyName]).SetValues(mergedComplexValues);}else //是普通里的話就和當前值、數據庫值、原始值各種對比。修改了就賦值{if (!object.Equals(current[propertyName], original[propertyName]))result[propertyName] = current[propertyName];else if (!object.Equals(database[propertyName], original[propertyName]))result[propertyName] = database[propertyName];}}return result;}

看看sql:

exec sp_executesql N'update [dbo].[Trips] set [Description] = @0, [CostUSD] = @1 where (([Identifier] = @2) and ([RowVersion] = @3)) select [RowVersion] from [dbo].[Trips] where @@ROWCOUNT > 0 and [Identifier] = @2',N'@0 nvarchar(max) ,@1 decimal(18,2),@2 uniqueidentifier,@3 binary(8)',@0=N'Getaway in Vermont',@1=400.00,@2='CF2E6BD3-7393-440C-941A-9124C61CE04A',@3=0x00000000000007DC

看看結果:

用戶修改和ExecuteSqlCommand修改的都保存上了。

最后講一個更實用的東西:重寫上下文的SaveChanges方法記錄結果集里實體的各種增/刪/改。
先到BreakAwayContext類里添加一個屬性標識使用數據庫上下文的SaveChanges方法還是使用自定義的SaveChanges方法:public bool LogChangesDuringSave { get; set; }

來看一個方法:

/// <summary>/// 記錄結果集的各種:增 / 刪 /改/// </summary>private static void TestSaveLogging(){using (var context = new DbContexts.DataAccess.BreakAwayContext()){var canyon = (from d in context.Destinationswhere d.Name == "Grand Canyon"select d).Single();//加載主表數據 context.Entry(canyon).Collection(d => d.Lodgings).Load();//顯示加載出從表相關數據canyon.TravelWarnings = "Take a hat!";//修改主表字段context.Lodgings.Remove(canyon.Lodgings.First());//刪除相關聯從表的第一條數據context.Destinations.Add(new DbContexts.Model.Destination { Name = "Seattle, WA" });//添加一條主表數據context.LogChangesDuringSave = true; //設置標識,使用自定義的SaveChanges方法 context.SaveChanges();}}

增加、修改、刪除操作等都有。運行這個方法前需要在BreakAwayContext類里添加記錄的幫助類方法:

/// <summary>/// 記錄幫助類方法/// </summary>private void PrintPropertyValues(DbPropertyValues values, IEnumerable<string> propertiesToPrint, int indent = 1){foreach (var propertyName in propertiesToPrint){var value = values[propertyName];if (value is DbPropertyValues){Console.WriteLine("{0}- Complex Property: {1}", string.Empty.PadLeft(indent), propertyName);var complexPropertyValues = (DbPropertyValues)value;PrintPropertyValues(complexPropertyValues, complexPropertyValues.PropertyNames, indent + 1);}else{Console.WriteLine("{0}- {1}: {2}", string.Empty.PadLeft(indent), propertyName, values[propertyName]);}}}private IEnumerable<string> GetKeyPropertyNames(object entity){var objectContext = ((IObjectContextAdapter)this).ObjectContext;return objectContext.ObjectStateManager.GetObjectStateEntry(entity).EntityKey.EntityKeyValues.Select(k => k.Key);}

再在BreakAwayContext類里重寫下上下文的SaveChanges方法:

/// <summary>/// 重寫SaveChanges方法/// </summary>public override int SaveChanges(){if (LogChangesDuringSave) //根據表示判斷用重寫的SaveChanges方法,還是普通的上下文SaveChanges方法 {var entries = from e in this.ChangeTracker.Entries()where e.State != EntityState.Unchangedselect e; //過濾所有修改了的實體,包括:增加 / 修改 / 刪除foreach (var entry in entries){switch (entry.State){case EntityState.Added:Console.WriteLine("Adding a {0}", entry.Entity.GetType());PrintPropertyValues(entry.CurrentValues, entry.CurrentValues.PropertyNames);break;case EntityState.Deleted:Console.WriteLine("Deleting a {0}", entry.Entity.GetType());PrintPropertyValues(entry.OriginalValues, GetKeyPropertyNames(entry.Entity));break;case EntityState.Modified:Console.WriteLine("Modifying a {0}", entry.Entity.GetType());var modifiedPropertyNames = from n in entry.CurrentValues.PropertyNameswhere entry.Property(n).IsModifiedselect n;PrintPropertyValues(entry.CurrentValues, GetKeyPropertyNames(entry.Entity).Concat(modifiedPropertyNames));break;}}}return base.SaveChanges(); //返回普通的上下文SaveChanges方法}

運行結果為:

所有添加/修改/刪除都記錄下來了,這個可以方便我們在寫程序的時候做更細微的控制,畢竟EF對實體操作的依據就是實體的各種狀態。

本文源碼

EF DbContext 系列文章導航:
  • EF如何操作內存中的數據和加載外鍵數據:延遲加載、貪婪加載、顯示加載??本章源碼
  • EF里單個實體的增查改刪以及主從表關聯數據的各種增刪改查??本章源碼
  • 使用EF自帶的EntityState枚舉和自定義枚舉實現單個和多個實體的增刪改查??本章源碼
  • EF里查看/修改實體的當前值、原始值和數據庫值以及重寫SaveChanges方法記錄實體狀態??本章源碼
  • EF里如何定制實體的驗證規則和實現IObjectWithState接口進行驗證以及多個實體的同時驗證??本章源碼
  • 重寫ValidateEntity虛方法實現可控的上下文驗證和自定義驗證??本章源碼
  • posted on 2014-02-25 19:42 NET未來之路 閱讀(...) 評論(...) 編輯 收藏

    轉載于:https://www.cnblogs.com/lonelyxmas/p/3567534.html

    總結

    以上是生活随笔為你收集整理的EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态...的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。