要专业系统地学习EF推荐《你必须掌握的Entity Framework 6.x与Core 2.0》。这本书作者(汪鹏,Jeffcky)的博客:https://www.cnblogs.com/CreateMyself/
快照追踪、代理追踪、DetectChanges让我很疑惑
这一节内容看的真的相当疑惑。
上次我们了解到变更追踪两种方式:快照追踪、代理追踪
快照追踪通过将POCO和快照进行比较得到哪些内容被更改,EF才知道对数据库做哪种操作。
代理追踪方式没有生成快照,他是通过重写我们的POCO生成一个代理类,因为重写它可以加入自己的代码,实现更改通知的机制。
他们两者的性能没有什么区别。
那么我们看看快照追踪,它到底是怎么比较的?其实他是调用的DetectChanges方法。detectChanges是SaveChanges方法实现的一部分,当我们调用该SvaeChanges时会调用DetectChangs
但是并不是只有saveChanges会调用DetectChanges,还有如下的方法
DbSet.Find DbSet.Load DbSet.Remove DbSet.Add DbSet.Attach DbContext.SaveChanges DbContext.GetValidationErrors DbContext.Entity DbChangeTraker.Entries
通过上一节的内容我认识到,DetectChanges方法非常重要,快照追踪方式必须要调用它才行
那么这节内容的告诉我们:不需要调用DetectChanges方式也可以
那么,不调用DetectChanges方法,那么EF使用的是快照追踪还是代理追踪??
我只能猜测是这样一种情况:如果关闭自动追踪,而且还要正常操作数据,那么现在这种,就已经没有了追踪的概念
我这都已经到了猜测的地步了,所以大家请不要轻信我说的话
既然不需要调用DetectChanges方式也可以,那么我们来试一下。数据中没有任何变化
// 关闭自动追踪 ctx.Configuration.AutoDetectChangesEnabled = false; var store = ctx.Stores.FirstOrDefault(); Console.WriteLine(ctx.Entry(store).State); // Unchanged Console.WriteLine(store.Name); // 王银河商店 store.Name = "四海商店"; Console.WriteLine(ctx.Entry(store).State); // Unchanged ctx.SaveChanges();
所以要关闭自动追踪并且成功修改值,是有约束的。我们不能使用给属性赋值的方式来,而要调用API,像下面这样
// 关闭自动追踪 ctx.Configuration.AutoDetectChangesEnabled = false; var store = ctx.Stores.FirstOrDefault(); Console.WriteLine(ctx.Entry(store).State); // Unchanged Console.WriteLine(store.Name); // 王银河商店 //store.Name = "四海商店"; ctx.Entry(store).Property(x => x.Name).CurrentValue = "四海商店"; Console.WriteLine(ctx.Entry(store).State); // Modified ctx.SaveChanges();
但是我们为什么要关闭自动追踪呢?
看下面,不关闭自动追踪,我现在插入多条数据,那么每一次调用Add方法都会调用一次DetectChanges方法
我有很多疑问,因为我还是放不下那两种追踪方式,快照和代理
既然关闭了自动追踪,那肯定不是快照追踪,那就是代理?
也不是,因为代理要满足两个条件,而我们上面所修改的Name属性并没有用Virtual修饰。所以我只能认为,关闭自动给追踪使用API的方式修改实体,那么此时就没有快照和代理的概念
我们平时修改实体,是通过给属性赋值的方式来的。这样很方便,那么EF为了支持这种便利,就有了快照和代理追踪方式。
但是现在我们是通过API的方式来修改实体,也就是说我们绕过了这一层,用比较底层的方式来修改实体,也就不存在快照和代理的概念
我再强调一遍,请不要轻信我说的话,我这只是猜测。
对于关闭自动追踪要遵循两条规则
1.如果之前未被调用,在没有调用EF代码将离开上下文状态的情况下,DetectChanges需要被调用该
2.在任何时候,如果非EF代码更改了一个实体或者复杂对象的属性,DetectChanges都需要被调用
其实对于作者说的这两条规则,我真的看的非常懵!什么是EF代码什么又是非EF代码。我觉得说的就是属性赋值和使用propery(x => x.Name).CurrentValue这种方式。
我只能连蒙带猜地自圆其说,行,暂且就这样认为了。
那么我接下要做的就是,在插入大量数据的时候,比较一个两者的性能
对打开关闭Detect,和AddRange分别测试,插入5000条数据
打开自动追踪,默认打开 21140ms 17607ms 17258ms
var watct = new Stopwatch(); watct.Start(); List<Store> stores = new List<Store>(); for (int i = 0; i < 5000; i++) { ctx.Stores.Add(new Store { Name = "aaa", Address = "ddd" }); } ctx.SaveChanges(); watct.Stop(); Console.WriteLine(watct.ElapsedMilliseconds); // 21140ms 17607ms 17258ms
AddRange 1744ms 3387ms 1417ms 1381ms 2146ms 3793ms 3695ms
var watch = new Stopwatch(); watch.Start(); List<Store> stores = new List<Store>(); for (int i = 0; i < 5000; i++) { stores.Add(new Store { Name = "aaa", Address = "ddd" }); } ctx.Stores.AddRange(stores); ctx.SaveChanges(); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds);
关闭自动追踪 3068ms 2778ms 3577ms 1888ms 4078ms
ctx.Configuration.AutoDetectChangesEnabled = false; var watch = new Stopwatch(); watch.Start(); List<Store> stores = new List<Store>(); for (int i = 0; i < 5000; i++) { ctx.Stores.Add(new Store { Name = "aaa", Address = "ddd" }); } ctx.SaveChanges(); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds);
由此我们发现关闭和开始自动追踪性能上有很大的差距,那么为什么AddRange性能也很好?
现在来看另一个引入了EF源码的项目
现在我们主要看看AddRange添加和关闭自动追踪逐条添加有何区别。
首先我们应该知道AddRange其实也是逐条添加的
// 添加十条数据用addRange方法添加 ctx.Database.Log = Console.WriteLine; List<Order> orders = new List<Order>(); for (int i = 0; i < 10; i++) { orders.Add(new Order { OrderNO = "aaa", Description = "mmm" }); } ctx.Orders.AddRange(orders); ctx.SaveChanges();
上面我们可以看到,它确实是逐条添加的,但是AddRange方法会调用DetectChanges方法,但是不会真正的去执行扫描操作
我现在把DetectChanges的庐山真面目放出来
// <summary> // For every tracked entity which doesn't implement IEntityWithChangeTracker detect changes in the entity's property values // and marks appropriate ObjectStateEntry as Modified. // For every tracked entity which doesn't implement IEntityWithRelationships detect changes in its relationships. // The method is used internally by ObjectContext.SaveChanges() but can be also used if user wants to detect changes // and have ObjectStateEntries in appropriate state before the SaveChanges() method is called. // </summary> internal virtual void DetectChanges() { Console.WriteLine("进入了DetectChanges方法"); var entries = GetEntityEntriesForDetectChanges(); if (entries == null) { return; // AddRange()他会在这里跳出去 } if (TransactionManager.BeginDetectChanges()) { try { Console.WriteLine("开始改变实体状态"); // Populate TransactionManager.DeletedRelationshipsByGraph and TransactionManager.AddedRelationshipsByGraph DetectChangesInNavigationProperties(entries); // Populate TransactionManager.ChangedForeignKeys DetectChangesInScalarAndComplexProperties(entries); // Populate TransactionManager.DeletedRelationshipsByForeignKey and TransactionManager.AddedRelationshipsByForeignKey DetectChangesInForeignKeys(entries); // Detect conflicts between changes to FK and navigation properties DetectConflicts(entries); // Update graph and FKs TransactionManager.BeginAlignChanges(); AlignChangesInRelationships(entries); } finally { TransactionManager.EndAlignChanges(); TransactionManager.EndDetectChanges(); } } }
然而我们关闭自动追踪,并且逐条添加,那么它根本就不会调用DetectChanges方法
他们之间的区别就是这,我们可以看到使用AddRange好像更快一些。可能是在关闭自动追踪的基础上优化了一些。
来总结一下,我们可以通过关闭自动追踪来提供性能,但是对它有足够的了解。以免放生意向不到的问题。
在开发过程中,如果实体不是太多,那么就不用考虑这个问题。
况且添加集合EF提供了AddRange(),删除有RemoveRange(),批量删除我们上次用的那个第三方库EntityFramework.Extended
但是通过去了解更深入的东西肯定是很好的。