日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

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

發(fā)布時(shí)間:2023/11/30 数据库 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态... 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
EF里查看/修改實(shí)體的當(dāng)前值、原始值和數(shù)據(jù)庫(kù)值以及重寫SaveChanges方法記錄實(shí)體狀態(tài) 原文:EF里查看/修改實(shí)體的當(dāng)前值、原始值和數(shù)據(jù)庫(kù)值以及重寫SaveChanges方法記錄實(shí)體狀態(tài)

本文目錄

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

文章開(kāi)始前建議大家為了更好的記憶最好自己實(shí)現(xiàn)文中的所有方法。如果非要直接運(yùn)行我的demo,必要的時(shí)候需要恢復(fù)下數(shù)據(jù)庫(kù)數(shù)據(jù),否則找不到記錄。

之前的章節(jié)已經(jīng)演示了context.Entry方法可以拿到實(shí)體的狀態(tài)(EntityState),來(lái)看一個(gè)方法:

/// <summary>/// 單個(gè)實(shí)體的狀態(tài)/// </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方法有兩個(gè)重載,分別返回泛型DbEntityEntry<TEntity>和非泛型的DbEntityEntry,它們都可以監(jiān)測(cè)到實(shí)體的狀態(tài),并且通過(guò)DbEntityEntry還可以操作實(shí)體的當(dāng)前值、原始值和數(shù)據(jù)庫(kù)值。分別是:

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

來(lái)看一個(gè)例子:

/// <summary>/// 打印實(shí)體當(dāng)前、原始和數(shù)據(jù)庫(kù)值/// </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]);}}

方法分析:先從數(shù)據(jù)庫(kù)取出一個(gè)實(shí)體,然后修改其Name屬性,這個(gè)時(shí)候當(dāng)前值(Current)和原始值(Original)都有了,分別是:修改后的值(還沒(méi)提交,在內(nèi)存中)和從庫(kù)里取出來(lái)時(shí)實(shí)體的值。再使用Database.ExecuteSqlCommand執(zhí)行了一段修改此對(duì)象在數(shù)據(jù)庫(kù)中的值,這個(gè)時(shí)候數(shù)據(jù)庫(kù)值(Database)也有了變化,這個(gè)實(shí)體的三個(gè)值都不相同了。還沒(méi)看到打印結(jié)果,在執(zhí)行entry.GetDatabaseValues()方法時(shí)報(bào)了一個(gè)EntitySqlException錯(cuò):

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

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

結(jié)果分析:當(dāng)前值為方法里修改的值、原始值是從數(shù)據(jù)庫(kù)取出未做任何操作的值、數(shù)據(jù)庫(kù)值是此時(shí)數(shù)據(jù)庫(kù)里的值。當(dāng)然新添加的實(shí)體不會(huì)有原始值和數(shù)據(jù)庫(kù)值、刪除的實(shí)體也不會(huì)有當(dāng)前值,利用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) //標(biāo)記刪除的實(shí)體不會(huì)有當(dāng)前值 {Console.WriteLine("\nCurrent Values:");PrintPropertyValues(entry.CurrentValues);}if (entry.State != EntityState.Added) //新添加的時(shí)候不會(huì)有原始值和數(shù)據(jù)庫(kù)值 {Console.WriteLine("\nOriginal Values:");PrintPropertyValues(entry.OriginalValues);Console.WriteLine("\nDatabase Values:");PrintPropertyValues(entry.GetDatabaseValues());}}

為了測(cè)試重寫下PrintLodgingInfo方法:

/// <summary>/// 測(cè)試打印添加和刪除時(shí)實(shí)體當(dāng)前、原始和數(shù)據(jù)庫(kù)值/// </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); //默認(rèn)var davesDump = (from d in context.Lodgingswhere d.Name == "Dave's Dump"select d).Single();context.Lodgings.Remove(davesDump);PrintChangeTrackingInfo(context, davesDump); //測(cè)試刪除實(shí)體var newMotel = new DbContexts.Model.Lodging { Name = "New Motel" };context.Lodgings.Add(newMotel);PrintChangeTrackingInfo(context, newMotel); //測(cè)試新添加實(shí)體 }}

當(dāng)然上面打印實(shí)體類型的方法并不通用,修改第二個(gè)參數(shù)為object類型:

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

看看打印結(jié)果:

之前打印實(shí)體的各種屬性都是通過(guò)遍歷的形式(PrintPropertyValues方法)打印出來(lái),如果僅取某個(gè)字段當(dāng)然沒(méi)必要這么麻煩,可以使用GetValue<TValue>:

/// <summary>/// 打印實(shí)體單個(gè)屬性/// </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到實(shí)體:ToObject方法

/// <summary>/// 拷貝DbPropertyValues到實(shí)體: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方法創(chuàng)建Destination實(shí)例 }}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的實(shí)體并修改其TravelWarnings字段,然后調(diào)用PrintDestination方法打印當(dāng)前實(shí)體的各屬性,再查出此實(shí)體在數(shù)據(jù)庫(kù)里的值,并且通過(guò)ToObject方法把數(shù)據(jù)庫(kù)取出來(lái)的這個(gè)對(duì)象也轉(zhuǎn)換成了實(shí)體對(duì)象。這么轉(zhuǎn)有什么好處呢?這個(gè)通過(guò)ToObject轉(zhuǎn)換的Destination實(shí)例不會(huì)被數(shù)據(jù)庫(kù)上下文追蹤,所以對(duì)其做的任何改變都不會(huì)提交數(shù)據(jù)庫(kù)。看看打印結(jié)果:

修改DbPropertyValues當(dāng)前值:

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

/// <summary>/// 修改DbPropertyValues當(dāng)前值/// </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 }}

類似于索引器的方式賦值即可,賦值后實(shí)體的狀態(tài)已經(jīng)是Modified了,顯然已經(jīng)被上下文追蹤到了,這個(gè)時(shí)候調(diào)用上下文的SaveChanges方法將會(huì)提交到數(shù)據(jù)庫(kù)。那么如果只是想打印和修改實(shí)體狀態(tài)以供查看,并不像被提交到數(shù)據(jù)庫(kù)怎么辦?


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

/// <summary>/// 克隆實(shí)體: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 }}

設(shè)置實(shí)體的值:SetValues方法

當(dāng)然實(shí)體的當(dāng)前值、原始值和數(shù)據(jù)庫(kù)值都是可以相互復(fù)制的:

/// <summary>/// 設(shè)置實(shí)體的值: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; //標(biāo)記未修改 Console.WriteLine("Name: {0}", canyon.Name); //Grand Canyon}}

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

再看看如何使用SetValues方法實(shí)現(xiàn)之前說(shuō)的克隆實(shí)體:

/// <summary>/// 克隆實(shí)體: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的值到新對(duì)象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'

很明顯實(shí)體已經(jīng)被克隆了。

獲取和設(shè)置實(shí)體的單個(gè)屬性:Property方法

/// <summary>/// 獲取和設(shè)置實(shí)體的單個(gè)屬性: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"; //設(shè)置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 }}

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

/// <summary>/// 查詢實(shí)體被修改字段: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 }}}

前面的章節(jié)已經(jīng)講解了如何查詢一對(duì)一、一對(duì)多等關(guān)系的導(dǎo)航屬性了,還不了解的點(diǎn)這里。現(xiàn)在講講如何修改導(dǎo)航屬性:

/// <summary>/// 修改導(dǎo)航屬性(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);}}

打印結(jié)果:
Current Value After Load: Grand Canyon
Current Value After Change: Great Barrier Reef

注:上面的方法并沒(méi)有調(diào)用上下文的SaveChanges方法,故程序跑完數(shù)據(jù)也不會(huì)保存到數(shù)據(jù)庫(kù),本文所有方法僅作演示都未提交數(shù)據(jù)庫(kù)。

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

/// <summary>/// 修改導(dǎo)航屬性(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);}}

打印結(jié)果:
Payments Before Add: 1
Payments After Add: 2

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

/// <summary>/// 取當(dāng)前最新的數(shù)據(jù)庫(kù)值: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(); //取出實(shí)體context.Database.ExecuteSqlCommand(@"UPDATE dbo.Lodgings SET Name = 'Le Grand Hotel' WHERE Name = 'Grand Hotel'"); //立馬修改實(shí)體值(這個(gè)時(shí)候數(shù)據(jù)庫(kù)中的值已改變,但是取出來(lái)放在內(nèi)存中的值并沒(méi)改變)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);}}

打印結(jié)果:
Name Before Reload: Grand Hotel
State Before Reload: Unchanged
Name After Reload: Le Grand Hotel
State After Reload: Unchanged

可以看出Reload方法已經(jīng)重新取出了數(shù)據(jù)庫(kù)中的最新值。來(lái)看看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)

當(dāng)然Reload方法也會(huì)保存內(nèi)存中修改的數(shù)據(jù),這個(gè)并不會(huì)沖突。在方法里的linq查詢后面加上:hotel.Name = "A New Name"; 打印結(jié)果就是這樣的了:
Name Before Reload: A New Name
State Before Reload: Modified
Name After Reload: Le Grand Hotel
State After Reload: Unchanged

注意,代碼里修改的Name已經(jīng)顯示了,并且標(biāo)記實(shí)體狀態(tài)為Modified了,Modified會(huì)在調(diào)用上下文的SaveChanges方法的時(shí)候提交到數(shù)據(jù)庫(kù)。這個(gè)過(guò)程是這樣的:

加載實(shí)體到內(nèi)存中 - 在內(nèi)存中對(duì)實(shí)體的某個(gè)屬性進(jìn)行修改 - 使用ExecuteSqlCommand方法執(zhí)行sql修改數(shù)據(jù)庫(kù)里該實(shí)體的值 - 調(diào)用Reload取出數(shù)據(jù)庫(kù)里本實(shí)體的最新值 - 調(diào)用SaveChanges方法的話,在內(nèi)存中對(duì)實(shí)體的修改也會(huì)被提交到數(shù)據(jù)庫(kù)

之前操作了單個(gè)實(shí)體,現(xiàn)在看看如何讀取關(guān)聯(lián)實(shí)體和狀態(tài)。使用DbContext.ChangeTracker.Entries方法:

     /// <summary>/// 讀取相關(guān)聯(lián)的實(shí)體和狀態(tài):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);}}}

添加了一個(gè)從表實(shí)體,并讀取所有關(guān)聯(lián)實(shí)體和其狀態(tài),打印結(jié)果:
Entity Type: DbContexts.Model.Payment - State: Added
Entity Type: DbContexts.Model.Reservation - State: Unchanged
Entity Type: DbContexts.Model.Payment - State: Unchanged

EF里如何解決更新數(shù)據(jù)時(shí)的沖突

正常根據(jù)實(shí)體的主鍵修改實(shí)體的時(shí)候,EF是不會(huì)判斷數(shù)據(jù)修改之前有沒(méi)有被別的人修改過(guò),但是如果做了并發(fā)控制,EF在更新某條記錄的時(shí)候才會(huì)拋錯(cuò)。這個(gè)系列文章的demo里有兩個(gè)實(shí)體做了并發(fā)控制:Person類的SocialSecurityNumber字段被標(biāo)記了ConcurrencyCheck;Trip類的RowVersion字段被標(biāo)記了Timestamp。來(lái)寫一個(gè)觸發(fā)DbUpdateConcurrencyException異常的方法并處理這個(gè)異常:

/// <summary>/// 修改實(shí)體/// </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);}}

方法分析:取出實(shí)體 - 修改實(shí)體Description屬性(此時(shí)實(shí)體狀態(tài)為Modified)- 使用ExecuteSqlCommand執(zhí)行sql修改了CostUSD和Description字段(修改后時(shí)間戳已經(jīng)不同了,PS:使用ExecuteSqlCommand執(zhí)行sql不需要調(diào)用SaveChanges方法)- 調(diào)用上下文的SaveChanges方法保存之前被標(biāo)記為Modified的實(shí)體,這個(gè)時(shí)候就會(huì)報(bào)一個(gè)DbUpdateConcurrencyException的異常,因?yàn)闀r(shí)間戳列已經(jīng)找不到了,這個(gè)更新的where條件根本找不到記錄了。有時(shí)間戳的列更新都是雙條件,時(shí)間戳詳細(xì)用法點(diǎn)這里了解。

嘗試寫個(gè)方法解決這個(gè)沖突:

/// <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); //從庫(kù)里取出來(lái)時(shí)的值var databaseValues = entry.GetDatabaseValues(); //即時(shí)數(shù)據(jù)庫(kù)的值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); //拷貝數(shù)據(jù)庫(kù)值到當(dāng)前值(恢復(fù)時(shí)間戳)break;case "D":entry.Reload(); //重新加載break;case "M":var mergedValues = MergeValues(entry.OriginalValues, entry.CurrentValues, databaseValues);//合并entry.OriginalValues.SetValues(databaseValues); //拷貝數(shù)據(jù)庫(kù)值到當(dāng)前值(恢復(fù)時(shí)間戳)entry.CurrentValues.SetValues(mergedValues); //拷貝合并后的值到當(dāng)前值,最終保存的是當(dāng)前值break;default:throw new ArgumentException("Invalid option");}}}

捕獲到異常后告知用戶要修改實(shí)體的原始值(用戶修改前從數(shù)據(jù)庫(kù)取出來(lái)的值)、現(xiàn)在的值(用戶修改的值)、數(shù)據(jù)庫(kù)里的值(此時(shí)數(shù)據(jù)庫(kù)里的值,這個(gè)值已被修改,不是用戶修改前取出來(lái)的值了),打印出來(lái)的結(jié)果顯示已經(jīng)有人修改了這條記錄了。最后是問(wèn)用戶是否保存修改。分別是保存、放棄、合并修改。

用戶輸入"S"表示“保存”,case語(yǔ)句塊里執(zhí)行的操作是拷貝數(shù)據(jù)庫(kù)值到原始值,這里該有疑惑了,調(diào)用SaveChanges方法保存的也是currentValues當(dāng)前值,跟databaseValues數(shù)據(jù)庫(kù)值還有OriginalValues原始值沒(méi)有任何關(guān)系啊。其實(shí)這么操作是恢復(fù)一下時(shí)間戳的值方便更新,之前說(shuō)過(guò)timestamp的列更新條件是兩個(gè),任何一個(gè)不對(duì)都更新不了。看看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

結(jié)果只保存了自己的修改:

用戶輸入“D”表示“放棄”,case語(yǔ)句塊里執(zhí)行的是Reload方法,這個(gè)方法之前已經(jīng)介紹過(guò)了,是重新加載數(shù)據(jù)庫(kù)里的最新值(Latest Value)。恢復(fù)下數(shù)據(jù)庫(kù)數(shù)據(jù)再執(zhí)行下方法,看看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)

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

上面的“保存修改”和“放棄修改”只能保存一個(gè),如果讓用戶修改的和ExecuteSqlCommand的修改同時(shí)生效呢,選擇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) //判斷當(dāng)前列是否復(fù)雜類型(很少) {var mergedComplexValues =MergeValues((DbPropertyValues)original[propertyName],(DbPropertyValues)current[propertyName],(DbPropertyValues)database[propertyName]); //是復(fù)雜類型的話就使用遞歸合并復(fù)雜類型的值((DbPropertyValues)result[propertyName]).SetValues(mergedComplexValues);}else //是普通里的話就和當(dāng)前值、數(shù)據(jù)庫(kù)值、原始值各種對(duì)比。修改了就賦值{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

看看結(jié)果:

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

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

來(lái)看一個(gè)方法:

/// <summary>/// 記錄結(jié)果集的各種:增 / 刪 /改/// </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();//加載主表數(shù)據(jù) context.Entry(canyon).Collection(d => d.Lodgings).Load();//顯示加載出從表相關(guān)數(shù)據(jù)canyon.TravelWarnings = "Take a hat!";//修改主表字段context.Lodgings.Remove(canyon.Lodgings.First());//刪除相關(guān)聯(lián)從表的第一條數(shù)據(jù)context.Destinations.Add(new DbContexts.Model.Destination { Name = "Seattle, WA" });//添加一條主表數(shù)據(jù)context.LogChangesDuringSave = true; //設(shè)置標(biāo)識(shí),使用自定義的SaveChanges方法 context.SaveChanges();}}

增加、修改、刪除操作等都有。運(yùn)行這個(gè)方法前需要在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) //根據(jù)表示判斷用重寫的SaveChanges方法,還是普通的上下文SaveChanges方法 {var entries = from e in this.ChangeTracker.Entries()where e.State != EntityState.Unchangedselect e; //過(guò)濾所有修改了的實(shí)體,包括:增加 / 修改 / 刪除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方法}

運(yùn)行結(jié)果為:

所有添加/修改/刪除都記錄下來(lái)了,這個(gè)可以方便我們?cè)趯懗绦虻臅r(shí)候做更細(xì)微的控制,畢竟EF對(duì)實(shí)體操作的依據(jù)就是實(shí)體的各種狀態(tài)。

本文源碼

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

    轉(zhuǎn)載于:https://www.cnblogs.com/lonelyxmas/p/3567534.html

    總結(jié)

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

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    亚洲午夜精品久久久 | 免费av大全| 日本韩国精品一区二区在线观看 | 一区二区视频在线观看免费 | 在线影院中文字幕 | 日日夜夜天天综合 | 日韩一区二区免费在线观看 | 精品999在线| 99精品国产高清在线观看 | 久久综合加勒比 | 日本久久久久久 | 夜夜爽88888免费视频4848 | 不卡av在线 | 成人黄色电影视频 | 国产精品久久久久久久久久久久午 | 免费麻豆网站 | 亚洲永久免费av | 黄色大片日本免费大片 | 免费看三级网站 | 国内丰满少妇猛烈精品播 | 黄色毛片在线 | 欧美久久久久久久久久久久 | av免费观看高清 | 久热精品国产 | 99色网站| 国产999精品久久久久久 | 香蕉视频国产在线观看 | 精品久久福利 | 97免费在线视频 | 欧美精品乱码99久久影院 | 亚洲欧美日韩国产一区二区三区 | 久久免费视屏 | 日本久久久久久久久久 | 色综合五月 | 精品视频一区在线 | 伊人婷婷在线 | 色欧美日韩 | 伊人永久| 欧美一区日韩精品 | 久久国产精品视频观看 | 亚洲一区精品人人爽人人躁 | 免费看网站在线 | 夜夜躁日日躁狠狠躁 | 91桃色免费视频 | 国产精品久久久久久久久久久久午夜片 | 中文字幕专区高清在线观看 | 久久色在线播放 | 久久免费播放视频 | 国产亚洲精品v | 国产艹b视频 | 91亚洲影院 | 揉bbb玩bbb少妇bbb | 国产成人av | 久久少妇av | 免费亚洲黄色 | 免费看久久 | 国产精品毛片久久久久久久久久99999999 | 亚洲成a人片77777kkkk1在线观看 | av电影一区 | 亚洲国产影院 | 91精彩视频| 天天操天天射天天插 | 麻豆国产在线播放 | av免费网 | 中文字幕av在线播放 | 日韩三级在线观看 | av五月婷婷 | 久久精品99精品国产香蕉 | 中文字幕在线观看免费观看 | 日日爽天天操 | 亚洲成人av电影 | 国产免费视频一区二区裸体 | 亚洲专区一二三 | 黄色三级免费观看 | 91av亚洲| 中文字幕av一区二区三区四区 | 国产精品欧美一区二区 | 在线蜜桃视频 | 福利一区二区三区四区 | 天天操天天爱天天干 | 国产 日韩 欧美 中文 在线播放 | 国产亚洲成av片在线观看 | 涩涩色亚洲一区 | 国产女人免费看a级丨片 | 麻豆视频免费入口 | 黄色高清视频在线观看 | 亚洲一区 av | 中文字幕不卡在线88 | 超碰公开在线观看 | 久久久久免费观看 | 黄色大全免费网站 | 在线不卡中文字幕播放 | 在线直播av | 婷婷在线精品视频 | 久日视频| 91九色最新地址 | 国产成人精品亚洲a | 色天天 | 久久 精品一区 | 日韩视频1区 | 亚洲综合在线五月天 | 日韩av一卡二卡三卡 | 99 久久久久 | 人人爽影院 | 在线看的毛片 | 中文字幕第一页av | 国产麻豆精品在线观看 | 日韩区欠美精品av视频 | 美女黄频网站 | 国产欧美精品在线观看 | 国产成人黄色片 | 免费av小说 | 成+人+色综合 | 91久久久久久国产精品 | 97超碰人人澡人人爱 | 国际精品久久 | 午夜视频亚洲 | 人人添人人 | 精品久久免费看 | 久久视频网 | 韩国av免费观看 | 在线免费高清一区二区三区 | 在线看黄色的网站 | 在线看毛片网站 | 又爽又黄又无遮挡网站动态图 | 2020天天干夜夜爽 | 国产精品激情 | 欧美成人va | aaa免费毛片| 综合网天天射 | 亚洲天天在线日亚洲洲精 | 天天射天天 | 免费视频xnxx com | 91在线一区 | 狠狠躁夜夜a产精品视频 | 久久综合影视 | 亚洲一区二区高潮无套美女 | 欧美另类xxx| 中文字幕丝袜一区二区 | 一区二区三区观看 | 亚洲欧美日韩一区二区三区在线观看 | 96久久久| 成人啪啪18免费游戏链接 | 欧美-第1页-屁屁影院 | 精品国自产在线观看 | 碰超在线观看 | 日韩黄色软件 | 日本中文字幕免费观看 | 免费视频xnxx com | 美女久久久久久久 | 中文字幕资源网在线观看 | 亚洲精品视频第一页 | 免费看国产视频 | 免费又黄又爽视频 | 国产在线精品视频 | 18pao国产成视频永久免费 | 天堂av一区二区 | 日韩特黄av | 一级一片免费观看 | 中文字幕 国产视频 | 成人免费网视频 | av网站免费线看精品 | 美女视频一区二区 | 日韩高清 一区 | 黄免费网站 | www.91av在线| 黄色免费观看视频 | 国产精品久久久久久久久费观看 | 国产一二三区在线观看 | 日本福利视频在线 | 久久字幕精品一区 | 久热免费 | 免费特级黄毛片 | 欧美激情视频三区 | 99热亚洲精品 | 在线观看视频国产一区 | 久久久久夜色 | 国产偷国产偷亚洲清高 | 国产在线视频在线观看 | 欧美日韩亚洲一 | 在线观看你懂的网站 | 久久精品国产一区二区三区 | 久久看免费视频 | 久久久久久高潮国产精品视 | 成人午夜电影网站 | 免费看毛片网站 | 免费99精品国产自在在线 | 伊人色综合久久天天 | 黄色av一区二区 | 欧美性久久久久久 | 国产无套一区二区三区久久 | 97人人模人人爽人人喊网 | 黄色小说在线免费观看 | 国产精品av在线免费观看 | 在线观看黄色国产 | 久久大片| 狠狠成人 | 最新免费av在线 | 色中文字幕在线观看 | 五月激情视频 | 久久久久久久久久久久影院 | 97电影在线 | 成人小视频在线播放 | 91精品国自产拍天天拍 | 精品视频在线观看 | 国产综合久久 | 在线观看黄色 | 人人干免费 | 韩日视频在线 | 久久亚洲视频 | 99精品色 | 91精品视频免费看 | 国产精品久久久久久久毛片 | 五月激情亚洲 | 免费视频色 | 91视频传媒 | 丁香六月在线观看 | 国产精彩视频一区二区 | 日日综合 | 特级xxxxx欧美 | 午夜色大片在线观看 | 日韩视频专区 | 噜噜色官网 | 国产精品成久久久久三级 | 97在线免费视频观看 | 天堂网av 在线 | 日韩高清精品免费观看 | 亚洲日本精品视频 | 日本黄色免费电影网站 | 久久久久久久久久久久久久av | 欧美大片在线观看一区 | 国产专区在线 | 成人中文字幕+乱码+中文字幕 | 日韩三级视频在线观看 | 伊人影院99| 三级黄色三级 | 丰满少妇对白在线偷拍 | 日韩免费一二三区 | 在线v片免费观看视频 | 久久久久久久av | 九九热精品视频在线观看 | www国产一区 | 婷婷精品国产欧美精品亚洲人人爽 | 国产精品久久久久久久久久久久午 | 国产精品美女久久久久aⅴ 干干夜夜 | 亚洲免费av片 | 波多野结衣在线观看一区二区三区 | 国产91影院| 国产精品99久久久久久有的能看 | 在线免费日韩 | 久久综合电影 | 又黄又爽又湿又无遮挡的在线视频 | 97视频在线观看播放 | 日韩一区在线播放 | 日本h在线播放 | 99riav1国产精品视频 | av丝袜美腿 | 欧美激精品 | 免费在线观看亚洲视频 | 亚洲精品视频在线观看免费 | 狠狠色狠狠色综合日日92 | 成人在线网站观看 | 久久久久久综合网天天 | 99精品视频在线观看免费 | 欧美成人精品xxx | 精品国产黄色片 | 伊人婷婷色 | 美女国产网站 | 最近日本韩国中文字幕 | 国产精品福利小视频 | av免费在线观看网站 | 97精品视频在线 | a√天堂中文在线 | 五月丁香 | 精品久久91 | 久久久精品视频成人 | 91精品国产自产老师啪 | 探花视频网站 | www.人人草 | av电影一区二区三区 | 亚洲五月婷婷 | 欧美精品久久99 | 麻豆视传媒官网免费观看 | 九九热1| www.xxx.性狂虐 | 最新av在线网址 | 久久久久久久久免费 | 在线观看一二三区 | 国产精品久久久久一区二区三区 | 免费一级片在线观看 | 精品国产免费看 | 草久久久久久久 | 日韩三级中文字幕 | 美女精品| 欧美激情综合网 | 久久99免费视频 | 国产女人18毛片水真多18精品 | 伊人天天狠天天添日日拍 | 黄色av一级| 国产精品18久久久久久首页狼 | 久久视频免费观看 | 夜夜骑首页 | www.97视频 | 亚洲日韩中文字幕在线播放 | 日韩精品欧美一区 | 欧美a级一区二区 | 久久精品一二三区白丝高潮 | 91av福利视频 | 99久久99久久免费精品蜜臀 | 伊人av综合| 91精品视频免费看 | 婷婷亚洲五月色综合 | 免费在线精品视频 | www好男人 | 免费成人av在线看 | av免费观看高清 | 久久精品99国产国产 | 色欲综合视频天天天 | 99精品视频免费在线观看 | 91精品国产福利 | 色婷婷狠狠五月综合天色拍 | 在线观看不卡视频 | 国产不卡av在线 | 91经典在线 | 激情婷婷网 | 久久久久久毛片 | 成人精品一区二区三区电影免费 | 日本久久免费视频 | 亚洲精品永久免费视频 | 久久精品网 | 色天天综合久久久久综合片 | 日日干网 | 久久免费视频一区 | 偷拍精偷拍精品欧洲亚洲网站 | 去干成人网 | 免费观看国产视频 | 夜夜躁狠狠躁 | 精品二区视频 | 国产精品正在播放 | 四虎小视频 | 黄色av网站在线观看免费 | 久久久www成人免费精品 | 爱干视频 | 狠狠伊人 | 国产精品一区二区无线 | 久久另类小说 | 99高清视频有精品视频 | 欧美日韩在线视频一区 | 伊人永久 | 免费在线激情电影 | 天堂资源在线观看视频 | 成人午夜片av在线看 | 黄色软件在线观看免费 | 日韩高清免费在线 | 成人宗合网 | 国产91学生粉嫩喷水 | av超碰在线| 日韩激情影院 | 国产成人综合精品 | 国产精品久久久久9999吃药 | 91在线免费公开视频 | 99久久精品免费看国产 | 九九视频网 | 2019中文最近的2019中文在线 | 欧美激情第八页 | 国产美女精品人人做人人爽 | 成年人视频免费在线播放 | 天天综合网国产 | 国产精品久久久久aaaa | .国产精品成人自产拍在线观看6 | 国产日本在线播放 | 黄色片软件网站 | 国产成人精品久久久久 | 久久久精品高清 | 亚洲精品xxx | 99在线看 | 色婷婷av一区| 欧美激情视频三区 | 成人香蕉视频 | 伊人天堂久久 | 人人爱人人舔 | 中文字幕免费高清在线观看 | 亚洲最新在线 | 久久免费在线观看视频 | 午夜精品99久久免费 | 夜夜躁狠狠躁日日躁视频黑人 | 国产成人99久久亚洲综合精品 | 九九免费精品视频在线观看 | 丰满少妇麻豆av | 日韩区视频 | 一区二区成人国产精品 | 国内精品久久久久 | 一区二区三区在线视频观看58 | 国内精品久久久久久中文字幕 | 91精品一区二区三区蜜桃 | 久久综合色影院 | 操操碰 | 久久观看最新视频 | 国产精品毛片一区二区三区 | 国产成人久久av免费高清密臂 | 国产麻豆精品95视频 | 日韩大片在线看 | 日日久视频 | 99视频在线看 | 国产精品扒开做爽爽的视频 | 91视频 - 88av | 日韩精品在线免费观看 | 久久在线免费观看视频 | 久久久久久久99精品免费观看 | 亚洲国产中文字幕在线观看 | 欧美一级片 | 国精产品999国精产品视频 | 黄色毛片视频免费观看中文 | 米奇狠狠狠888 | 99久久精品国产一区二区成人 | 亚洲91在线| 四虎影视www| 亚洲国产一区在线观看 | 久久久久综合精品福利啪啪 | 国产91九色蝌蚪 | 一级性视频 | 国精产品999国精产 久久久久 | 久久黄色免费 | 美女视频久久久 | 日日爽日日操 | 欧美天堂视频在线 | 日本中文在线播放 | 日韩av免费网站 | 免费看一级 | 国产伦精品一区二区三区高清 | 国产成人久久精品亚洲 | 婷婷久久综合网 | 亚洲精品玖玖玖av在线看 | 成人午夜网址 | 日韩视频免费观看高清完整版在线 | 国产精品theporn | 日韩 在线 | 97精品超碰一区二区三区 | 成年人免费在线 | 激情偷乱人伦小说视频在线观看 | 久久久久久久久久久免费 | 日韩欧美一区二区在线播放 | 一区二区三区在线看 | aa级黄色大片 | 91亚洲永久精品 | 婷婷网站天天婷婷网站 | 国产91欧美 | 国产视频一区精品 | 色综合天天视频在线观看 | 久久婷婷一区二区三区 | 五月开心六月婷婷 | 美女天天操| 国产 欧美 日本 | 99视频国产精品免费观看 | 欧美激情h| 国产午夜精品理论片在线 | 美女免费黄网站 | 免费在线观看亚洲视频 | 国产综合激情 | 国产午夜视频在线观看 | 久久久99精品免费观看app | 亚洲精品啊啊啊 | www.伊人色.com| 在线免费观看欧美日韩 | 激情六月婷婷久久 | 国产色婷婷精品综合在线手机播放 | 五月天六月丁香 | 粉嫩一区二区三区粉嫩91 | 天天天插 | 00av视频| 国产亚洲午夜高清国产拍精品 | 婷婷六月中文字幕 | 这里只有精品视频在线 | 天天综合成人 | 久久成人精品电影 | 国产96在线| 久久影院午夜论 | 久久久片| 91资源在线播放 | av观看网站| 91精品国产99久久久久 | 久久精品女人毛片国产 | 国内少妇自拍视频一区 | 精品xxx | 国产999精品久久久久久绿帽 | 国产精品久久久久一区二区国产 | 亚洲 欧美 综合 在线 精品 | 五月天综合网站 | 中文字幕资源站 | 日日日干| 亚洲永久国产精品 | 中文字幕在线观看免费高清完整版 | a级国产乱理伦片在线播放 久久久久国产精品一区 | 国产视频资源在线观看 | 国产精品综合久久久 | 欧美日韩一区二区免费在线观看 | 久久婷婷一区二区三区 | 国产精品久久久区三区天天噜 | 久久99精品国产麻豆宅宅 | 亚洲三级在线免费观看 | 精品理论片 | 色999精品| 国产成人高清 | 欧美乱码精品一区二区 | 天天操天天干天天操天天干 | 麻豆影视网 | 亚洲综合视频在线观看 | 亚洲精品动漫在线 | 国产精品一区二区62 | 久久免费看a级毛毛片 | 中文字幕制服丝袜av久久 | 久久精品免费看 | 一区中文字幕 | 久久tv | 欧美最猛性xxxxx(亚洲精品) | 久久新视频 | 国产99久久久国产精品免费二区 | 色综合久久88 | 国产视频2 | 日韩精品网址 | 亚洲激情精品 | 毛片激情永久免费 | 综合精品久久 | 日本在线h | 欧美精品一区二区免费 | 欧美日韩亚洲第一 | 日日骑| 日韩高清不卡在线 | 中文字幕在线观看第二页 | 日韩三区在线观看 | 国产成人久久精品 | 国产成人99av超碰超爽 | 粉嫩高清一区二区三区 | 亚洲电影图片小说 | 中文字幕一区二区三区在线播放 | 蜜臀av性久久久久蜜臀av | 免费特级黄色片 | 精品中文字幕在线观看 | 在线免费视频 你懂得 | 亚洲精品网站在线 | 波多野结衣电影一区 | 超碰.com | 97在线观看视频免费 | 黄污在线观看 | 黄色毛片在线看 | 一区三区在线欧 | 欧亚日韩精品一区二区在线 | 久久99精品久久只有精品 | 久久精品视频4 | 欧美一区二区三区激情视频 | 欧美色综合久久 | 免费看污黄网站 | 青青草国产成人99久久 | 黄色片毛片 | 国产精品一区二区免费 | 免费不卡中文字幕视频 | 久久精品艹| 中文字幕一区二区三区视频 | 亚洲午夜精品一区二区三区电影院 | 美女久久网站 | 人人爽人人爽人人爽学生一级 | 久久亚洲福利视频 | 免费观看性生活大片3 | 中文字幕在线观看av | 国产三级精品三级在线观看 | 久久免费国产精品1 | 97人人添人澡人人爽超碰动图 | 国产精品综合在线 | 久久无码精品一区二区三区 | 中文字幕第一 | 深爱婷婷| 人人看看人人 | 狠狠色噜噜狠狠狠 | 日本午夜在线亚洲.国产 | 中文在线免费一区三区 | 欧美成人精品在线 | 又色又爽又黄高潮的免费视频 | 九九爱免费视频 | 亚洲 欧美 精品 | 久久精品福利 | 国产99久久久国产精品成人免费 | 香蕉在线影院 | 久久99久久99精品免视看婷婷 | 在线不卡视频 | 成 人 免费 黄 色 视频 | 天天操福利视频 | 国产婷婷精品av在线 | 在线观看亚洲 | 国产在线一区二区 | 午夜国产福利在线 | 亚洲精品99久久久久中文字幕 | 91视频免费看 | 黄色精品久久 | 亚洲女裸体 | 久久精品福利 | 黄色av观看| 在线视频 你懂得 | 91精品久久久久久综合五月天 | 亚洲精品乱码久久久久久蜜桃不爽 | 一区二区三区 中文字幕 | 少妇精品久久久一区二区免费 | 婷婷激情网站 | 成人在线观看日韩 | 午夜视频免费播放 | 国产精品视频地址 | 九色视频网址 | 香蕉久草 | 精品久久久久久亚洲综合网站 | 国产黄网在线 | 亚洲a色 | 欧美人牲| 午夜精品一二区 | 国产一级在线看 | 一区二区视频在线播放 | 丁香花中文字幕 | 91av原创| 六月婷操 | 亚洲国产精品99久久久久久久久 | 久草电影免费在线观看 | 人人干人人爽 | 欧美成人高清 | 日本三级中文字幕在线观看 | 三级免费黄| 日本韩国精品一区二区在线观看 | 亚洲国产精品va在线看黑人动漫 | 在线观看播放av | 人人舔人人舔 | 免费黄色av电影 | 亚洲精品视频在线免费 | 九色激情网 | 91看片在线 | 亚洲综合色av | 中文字幕在线观看资源 | 一级片视频免费观看 | 国产专区一 | 中文字幕免费播放 | 日韩网站在线看片你懂的 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 天天爽夜夜操 | 婷婷射五月| 免费日韩一级片 | 中午字幕在线观看 | 九色在线| 国产在线色站 | 亚洲男男gaygayxxxgv | 日韩免费视频播放 | 日日干夜夜草 | 国产精品成人一区二区三区 | 成人一级片视频 | 欧美日韩国产一二三区 | 国产一区二区三区视频在线 | 国产精品自拍在线 | 在线观看爱爱视频 | 九九精品视频在线 | av在线在线| 日本女人逼 | 亚洲 中文 在线 精品 | 国产96在线 | 亚洲成人免费在线观看 | 欧美久草在线 | 欧美色综合天天久久综合精品 | 狠狠狠干狠狠 | 午夜av一区| 免费黄色网址网站 | 免费电影一区二区三区 | 丁香花在线视频观看免费 | 成人在线播放av | av中文字幕电影 | 欧美日韩色婷婷 | 丁香久久五月 | 久久精品中文字幕免费mv | 国产精品久久亚洲 | 国产福利av在线 | 中文字幕色婷婷在线视频 | 国产精品久久久久婷婷二区次 | 二区三区中文字幕 | 欧美精品天堂 | 国产在线精品一区二区不卡了 | 欧美日韩xxx | 在线观看www. | 伊人色综合久久天天 | 国产精品久久久久久久久软件 | 一区二区三区精品在线视频 | 97在线播放 | 国产精品女人久久久久久 | 超碰九九| 麻豆一区在线观看 | 欧美亚洲成人xxx | 成人高清av在线 | 久久伊人热 | 天天草夜夜 | av免费看在线 | 亚洲精品视频播放 | 久久99影院 | 四虎欧美 | 午夜精品久久久久久久久久 | 中文字幕在线观看视频一区 | 日韩精品一区二区三区在线视频 | 日本中文字幕在线播放 | 人人澡超碰碰 | 深夜免费福利在线 | 欧美天天综合网 | 一区二区三区不卡在线 | 婷婷在线色 | 高清国产午夜精品久久久久久 | 狠狠色丁香久久婷婷综合五月 | 黄色毛片视频免费 | 狠狠干狠狠艹 | 天天射天天操天天 | av成人免费观看 | 91九色蝌蚪视频网站 | 日韩高清毛片 | 久久久91精品国产一区二区三区 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 黄网站色欧美视频 | 欧美精彩视频在线观看 | 色999五月色 | 欧美日比视频 | 欧美另类老妇 | 天天看天天干 | 中文在线资源 | 啪啪资源 | 欧美一区二区三区免费观看 | 国产精品日韩久久久久 | 久久久久电影 | 99精品免费 | 国产视频午夜 | 国产亚洲欧美精品久久久久久 | 欧美一级免费黄色片 | 久久久麻豆 | 91av手机在线| 91在线免费播放 | 欧美日韩国产一二三区 | 亚洲狠狠丁香婷婷综合久久久 | 国产一级电影 | 99色免费 | 成片人卡1卡2卡3手机免费看 | 99精品亚洲 | 亚洲人人网 | 97电影在线观看 | 天堂av在线 | 99精品免费久久久久久久久 | 成人性生交大片免费观看网站 | 香蕉视频久久久 | 在线观看中文字幕亚洲 | 91亚洲狠狠婷婷综合久久久 | 午夜123 | 欧美黄在线 | 九色视频网址 | 日韩高清精品免费观看 | 国产综合香蕉五月婷在线 | 99中文字幕在线观看 | 干 操 插| 欧美坐爱视频 | 成人毛片在线观看 | 开心色停停 | 808电影 | 视频在线播放国产 | 亚洲综合欧美精品电影 | 精品一区二区在线观看 | 欧美一区二区三区在线播放 | 天天激情站 | 夜夜躁日日躁狠狠久久88av | 日韩av高清 | 最近免费中文字幕mv在线视频3 | 美女网站黄在线观看 | 欧美在线1| 久久久国产一区二区三区 | 久久久久国产一区二区三区 | 国产玖玖视频 | 天天综合网 天天 | av福利在线免费观看 | 国产精品久久久久久一区二区 | 天天干天天射天天爽 | 97精品超碰一区二区三区 | 午夜精品一区二区三区在线观看 | 日批视频在线观看免费 | 国内精品视频一区二区三区八戒 | 午夜精品一区二区三区可下载 | 高潮久久久久久 | 人人干97| 永久免费精品视频网站 | 国产成在线观看免费视频 | 激情欧美一区二区三区 | 一区二区三区四区五区六区 | 天天操天天干天天 | 久久99国产精品久久 | 欧美老女人xx | 久久影视一区 | 国产破处视频在线播放 | 欧美久草视频 | 欧美a级片网站 | 欧美极度另类性三渗透 | 欧美精品一级视频 | 激情xxxx| 欧美粗又大 | 欧美性色综合网站 | 狠狠插狠狠操 | 国产精品国产三级在线专区 | 久草观看 | 91精品国产自产在线观看永久 | 国产成人在线免费观看 | 成人丝袜| 黄色av影院 | 久久一区二| 婷婷福利影院 | 国产免费观看高清完整版 | 亚洲国产中文字幕在线观看 | 黄色电影网站在线观看 | 福利区在线观看 | 美女精品国产 | av成人动漫| 国产亚洲精品久久久久久 | 免费在线观看一区 | 久久综合免费 | 91精品啪在线观看国产 | 国产精品久久久久久久久久久久久 | 国产亚洲精品久久久久动 | 久久久久久久久久久影院 | 天天艹天天爽 | 亚洲国产精品电影 | 国产麻豆果冻传媒在线观看 | 久久影院精品 | 欧美在线观看视频一区二区 | 国产看片网站 | 不卡电影免费在线播放一区 | 国内一级片在线观看 | 九九精品毛片 | 久久久免费毛片 | 色婷婷亚洲综合 | 在线涩涩| 天天操夜夜曰 | 丁香视频| 国产网站色 | 久热免费在线 | 久久久资源 | 777奇米四色| 久草在线免费资源站 | 在线观看黄av | 美女在线免费观看视频 | 亚洲精品短视频 | www久 | 午夜精品视频在线 | 日韩专区在线播放 | 色噜噜在线观看 | 又色又爽的网站 | 在线观看免费视频你懂的 | www.天天操.com | 久久免费视频观看 | 精品国产乱码久久久久久三级人 | 正在播放 久久 | 在线免费色 | 色久网| 一区二区三区免费在线观看视频 | 日本大片免费观看在线 | 中文字幕色在线 | 狠狠狠干 | 正在播放国产一区二区 | 国产精品福利在线播放 | 久久久精品欧美一区二区免费 | 亚洲日本在线一区 | 91成人在线视频 | 伊人国产视频 | 91麻豆精品 | 精品久久久久免费极品大片 | 国产高清在线看 | 欧美精品久久久久久久免费 | 亚洲美女在线一区 | 国产 日韩 欧美 自拍 | 午夜精品久久一牛影视 | 午夜视频导航 | 很黄很污的视频网站 | 天天操夜夜操国产精品 | 不卡视频国产 | 亚洲精品动漫久久久久 | 日日躁夜夜躁xxxxaaaa | 欧美激情h | 国产精品福利午夜在线观看 | 免费在线观看国产精品 | 91精品国产乱码在线观看 | 亚洲欧美视频在线播放 | 99久免费精品视频在线观看 | 在线免费高清一区二区三区 | 国产网红在线观看 | 国产精品男女 | 日日草av | 午夜影院一级片 | 精品久久久久国产免费第一页 | 国产精品一区二区白浆 | 国产视 | 最近中文字幕国语免费高清6 | 成人av观看| 欧美a级成人淫片免费看 | 中文av在线免费观看 | japanesefreesexvideo高潮| 中文字幕一区2区3区 | 国产精品久久片 | 色婷婷88av视频一二三区 | 一级黄视频| 国产97在线视频 | 久久久精品国产一区二区电影四季 | 欧美 亚洲 另类 激情 另类 | 久久九九影院 | 黄色免费大片 | 伊人久久精品久久亚洲一区 | 精品黄色在线观看 | 国产日本在线播放 | 精品一区在线看 | 国产精品免费观看国产网曝瓜 | 91麻豆精品国产91久久久更新时间 | www.人人草 | 少妇性bbb搡bbb爽爽爽欧美 | 91禁在线观看 | 91aaa在线观看| 国产成人精品三级 | 成人一级电影在线观看 | 97超碰网| 中文字幕麻豆 | 久草国产在线观看 | 久久久国产精品网站 | 五月天久久婷婷 | 丁香六月欧美 | av中文字幕av | 久久99精品久久久久久清纯直播 | 在线播放第一页 | 日韩啪视频 | 欧美黄色免费 | 国内久久精品 | 久久精品视频在线播放 | 在线视频区 | 免费黄色a网站 | 午夜精品一二三区 | 精品国产一区二区三区久久 | 午夜国产福利视频 | 久草精品国产 | 在线国产91 | 色婷婷综合久久久 | 免费在线观看黄 | 午夜少妇 | 国产亚洲精品久久 | 亚洲 综合 激情 | 免费看的黄网站 | 97色狠狠 | 99久久网站 | 男女啪啪免费网站 | 亚洲精品视频网站在线观看 | 黄色小说在线免费观看 | 国产三级国产精品国产专区50 | 国产区在线视频 | 一二三区av| 91xav| 国产精品久久久久永久免费观看 | 国产福利一区在线观看 | 在线激情影院一区 | 久久精品一区二区三 | 丰满少妇在线观看网站 | 色久五月 | 夜夜骑日日 | 人人藻人人澡人人爽 | 久久精精品视频 | 激情久久小说 | 五月激情婷婷丁香 | 97视频在线 | 激情婷婷色 | 亚洲精品tv久久久久久久久久 | 久久艹中文字幕 | 操一草 | 91丨九色丨国产女 | 国产福利91精品一区二区三区 | 91成人精品观看 | a黄在线观看 | 在线日韩av | 69国产盗摄一区二区三区五区 | 天天天干天天天操 | 国产在线精品一区二区不卡了 | 91成人精品一区在线播放69 | 六月色丁 | www国产亚洲 | 日韩国产精品久久久久久亚洲 | 国产麻豆剧传媒免费观看 | 久久成人欧美 | 成年人在线观看视频免费 | 国产无套精品久久久久久 | 国产精品99精品久久免费 | 亚洲成人黄色在线观看 | 久久与婷婷 | 日本一区二区三区免费观看 | 日韩一区精品 | 日韩在线视频一区 | 亚洲黄色三级 | 欧美日韩在线第一页 | 国产99久久久精品视频 | 狠狠躁夜夜a产精品视频 | 久久免费精品视频 | 欧美a在线免费观看 | 精品视频在线免费观看 | 99热手机在线观看 | 亚洲区精品 |