EF6学习笔记十八:DetectChanges

要专业系统地学习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();
View Code

 所以要关闭自动追踪并且成功修改值,是有约束的。我们不能使用给属性赋值的方式来,而要调用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();
View Code

 但是我们为什么要关闭自动追踪呢?

看下面,不关闭自动追踪,我现在插入多条数据,那么每一次调用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
View Code

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);   
View Code

 关闭自动追踪   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); 
View Code

 由此我们发现关闭和开始自动追踪性能上有很大的差距,那么为什么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();
View Code

 上面我们可以看到,它确实是逐条添加的,但是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();
                }
            }
        }
View Code

 然而我们关闭自动追踪,并且逐条添加,那么它根本就不会调用DetectChanges方法

他们之间的区别就是这,我们可以看到使用AddRange好像更快一些。可能是在关闭自动追踪的基础上优化了一些。

 来总结一下,我们可以通过关闭自动追踪来提供性能,但是对它有足够的了解。以免放生意向不到的问题。

在开发过程中,如果实体不是太多,那么就不用考虑这个问题。

况且添加集合EF提供了AddRange(),删除有RemoveRange(),批量删除我们上次用的那个第三方库EntityFramework.Extended

但是通过去了解更深入的东西肯定是很好的。

猜你喜欢

转载自www.cnblogs.com/jinshan-go/p/10310772.html
EF6