Entity Framework Code First经过DbContext.ChangeTracker对实体对象的变更进行跟踪,实现跟踪的方式有两种:变更跟踪快照和变更跟踪代理。html
变更跟踪快照:前面几篇随笔的示例都是经过实体对象变更快照跟踪来实现数据操做的,POCO模型不包含任何逻辑去通知Entity Framework实体类属性的变更。Entity Framework在第一次对象加载到内存中时进行一次快照,添加快照发生在返回一次查询或添加一个对象到DbSet中时。当Entity Framework须要知道对象的变更时,将先把当前实体与快照中的对象进行扫描对比。实现扫描对比的方法是调用DbContext.ChangeTracker的DetectChanges方法。sql
变更跟踪代理:变更跟踪代理是一种会主动通知Entity Framework实体对象发生变更的机制。如:延迟加载的实现方式。要使用变更跟踪代理,须要在定义的类结构中,Entity Framework能够在运行时从POCO类中建立动态类型并重写POCO属性。动态代理就是一种动态类型,包含重写属性和通知Entity Framework实体对象变更的逻辑。数据库
Entity Framework Code First中可以自动调用DbContext.ChangeTracker.DetectChanges的方法:性能
◊ DbSet.Add测试
◊ DbSet.Findspa
◊ DbSet.Remove代理
◊ DbSet.Attachcode
◊ DbSet.Localhtm
◊ DbContext.SaveChanges对象
◊ DbContext.GetValidationErrors
◊ DbContext.Entry
◊ DbChangeTracker.Entries
◊ 任何在DbSet上进行LINQ的查询
一、控制什么时间调用DetectChanges
大部分的实例对象的变更调整须要在Entity Framework进行SaveChanges时才会知道,但也能够根据须要调用变更跟踪获取当前对象的状态。
Entity Framework Code First的DbContext.DetectChanges在检测实例对象的变更时,大部分状况不会有性能的问题。但当有大量的实例对象在内存中,或DbContext有大量的操做时,自动的DetectChanges行为可能会必定程度的影响性能。Entity Framework提供关闭自动的DetectChanges的功能,在须要的时候进行手动调用。
using (var ctx = new PortalContext()) { ctx.Configuration.AutoDetectChangesEnabled = false; }
示例:
using (var ctx = new PortalContext()) { ctx.Configuration.AutoDetectChangesEnabled = false; var province = ctx.Provinces.Find(1); province.ProvinceName = "测试"; Console.WriteLine("Before DetectChanges:{0}", ctx.Entry(province).State); ctx.ChangeTracker.DetectChanges(); Console.WriteLine("After DetectChanges:{0}", ctx.Entry(province).State); }
代码运行结果:
Before DetectChanges:Unchanged After DetectChanges:Modified
二、获取不带变更跟踪的实体查询
在一些状况下,咱们只须要查询返回一个只读的数据记录,而不会对数据记录进行任何的修改。这种时候不但愿Entity Framework进行没必要要的状态变更跟踪,可使用Entity Framework的AsNoTracking方法来查询返回不带变更跟踪的查询结果。
using (var ctx = new PortalContext()) { foreach (var province in ctx.Provinces.AsNoTracking()) { Console.WriteLine(province.ProvinceName); } }
以上代码中使用AsNoTracking方法查询返回无变更跟踪的Province的DbSet,因为是无变更跟踪,因此对返回的Province集中数据的任何修改,在SaveChanges()时,都不会提交到数据库中。
AsNoTracking是定义在IQueryable<T>中的扩展方法,因此也能够用于LINQ表达式查询。
using (var ctx = new PortalContext()) { var query = from p in ctx.Provinces.AsNoTracking() where p.ProvinceID > 10 select p; foreach (var province in query) { Console.WriteLine(province.ProvinceName); } }
或
using (var ctx = new PortalContext()) { var query = from p in ctx.Provinces where p.ProvinceID > 10 select p; query = query.AsNoTracking(); foreach (var province in query) { Console.WriteLine(province.ProvinceName); } }
注:使用AsNoTracking须要添加引用命名空间using System.Data.Entity。
三、单个实体的变更跟踪信息及操做
使用状态属性:
using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(10); DbEntityEntry<Province> entry = ctx.Entry(province); Console.WriteLine("Before Edit: {0}", entry.State); province.ProvinceName = "Test"; ctx.ChangeTracker.DetectChanges(); Console.WriteLine("After Edit: {0}", entry.State); }
注:DbEntityEntry须要引用命名空间using System.Data.Entity.Infrastructure;
代码运行结果为:
Before Edit: Unchanged After Edit: Modified
四、查看对象的当前值、原始值及数据库中的值
经过DbEntityEntry能够获取对象的当前、原始及在数据库中的值,DbPropertyValues则用于保存对象具体的属性。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.Entity; using System.Data.Entity.Infrastructure; using Portal.Models; namespace Portal { class Program { static void Main(string[] args) { using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(10); province.ProvinceName = "Test"; ctx.Database.ExecuteSqlCommand("UPDATE Province SET ProvinceName = 'Testing' WHERE ProvinceID = 10"); PrintChangeTrackingInfo(ctx, province); } } static void PrintChangeTrackingInfo(DbContext ctx, Province province) { var entry = ctx.Entry(province); Console.WriteLine(entry.State); Console.WriteLine("\nCurrent Values:"); PrintPropertyValues(entry.CurrentValues); Console.WriteLine("\nOriginal Values:"); PrintPropertyValues(entry.OriginalValues); Console.WriteLine("\nDatabase Values:"); PrintPropertyValues(entry.GetDatabaseValues()); } static void PrintPropertyValues(DbPropertyValues values) { foreach (var propertyName in values.PropertyNames) { Console.WriteLine("- {0}-{1}", propertyName, values[propertyName]); } } } }
代码运行的结果:
Modified Current Values: - ProvinceID-10 - ProvinceNo-320000 - ProvinceName-Test Original Values: - ProvinceID-10 - ProvinceNo-320000 - ProvinceName-测试 Database Values: - ProvinceID-10 - ProvinceNo-320000 - ProvinceName-Testing 请按任意键继续. . .
代码运行所执行的SQL语句:
exec sp_executesql N'SELECT [Limit1].[ProvinceID] AS [ProvinceID], [Limit1].[ProvinceNo] AS [ProvinceNo], [Limit1].[ProvinceName] AS [ProvinceName] FROM ( SELECT TOP (2) [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName] FROM [dbo].[Province] AS [Extent1] WHERE [Extent1].[ProvinceID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=10
UPDATE Province SET ProvinceName = 'Testing' WHERE ProvinceID = 10
exec sp_executesql N'SELECT [Limit1].[ProvinceID] AS [ProvinceID], [Limit1].[ProvinceNo] AS [ProvinceNo], [Limit1].[ProvinceName] AS [ProvinceName] FROM ( SELECT TOP (2) [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName] FROM [dbo].[Province] AS [Extent1] WHERE [Extent1].[ProvinceID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=10
从代码运行所执行的SQL语句能够看出,在最后获取对象在数据库中的值时,Entity Framework再一次到数据库中去查询对象的记录值。
五、修改DbPropertyValues中的值
DbPropertyValues中的值不是只读,故能够在第一次加载以后进行修改。
using (var ctx = new PortalContext()) { ctx.Configuration.AutoDetectChangesEnabled = false; var province = ctx.Provinces.Find(10); ctx.Entry(province) .CurrentValues["ProvinceName"] = "测试"; Console.WriteLine("Property Value:{0}", province.ProvinceName); Console.WriteLine("State:{0}", ctx.Entry(province).State); }
运行结果:
Property Value:测试 State:Modified
在上面的代码中,尽管已经禁用了自动侦测变更,但在修改了属性值以后,对象的属性仍修改成Modified。实体属性的修改是经过Change Tracker API实现的,实体的状态在不须要调用DetectChanges即修改成Modified。
若不但愿实体的状态发生改变,则实现方式为:
using (var ctx = new PortalContext()) { ctx.Configuration.AutoDetectChangesEnabled = false; var province = ctx.Provinces.Find(10); var _province = ctx.Entry(province).CurrentValues.Clone(); _province["ProvinceName"] = "测试"; Console.WriteLine("Property Value:{0}", province.ProvinceName); Console.WriteLine("State:{0}", ctx.Entry(province).State); }
运行结果:
Property Value:Test State:Unchanged