• 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

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

  • 相关阅读:
    如何完成看似不可能完成的任务
    SQL Server 2008 数据挖掘算法
    混在北京
    09年的一个方案,很遗憾没有采纳,回头看看,我还认为我是对的
    Metro Home
    InputScope
    Mozart Update 3 (7392…)
    搏斗Mango beta…
    Mango 7712 is coming
    HD2 update NODO
  • 原文地址:https://www.cnblogs.com/jinshan-go/p/10310772.html
Copyright © 2020-2023  润新知