zoukankan      html  css  js  c++  java
  • EF Core 原理从源码出发(二)

    紧接着我的上一篇博客,可以点击这里回到上一篇博客,上回分析到ef 两个重要的对象,StateManager和ChangeTracker这个对象,当我们向DbContext添加对象的时候我们会调用如下代码。

     1         private EntityEntry<TEntity> SetEntityState<TEntity>(
     2             TEntity entity,
     3             EntityState entityState)
     4             where TEntity : class
     5         {
     6             var entry = EntryWithoutDetectChanges(entity);
     7 
     8             SetEntityState(entry.GetInfrastructure(), entityState);
     9 
    10             return entry;
    11         }

    在第6行的时候我们会得到一个entity,如上一篇博客所说,这个entity 会被记录detach状态(因为是new的状态)并记录在detachReferenceMap中,在第七行的代码会将这个实体对象标记为add方法,但是需要考虑的情况有很多,比如是否被修改,是否添加完再修改,主键生成,外键联系,导航属性处理等等,这些都是一些棘手的操作,让我们看一下第七行的代码具体逻辑吧。

     1     if (entry.EntityState == EntityState.Detached)
     2             {
     3                 DbContextDependencies.EntityGraphAttacher.AttachGraph(
     4                     entry,
     5                     entityState,
     6                     entityState,
     7                     forceStateWhenUnknownKey: true);
     8             }
     9             else
    10             {
    11                 entry.SetEntityState(
    12                     entityState,
    13                     acceptChanges: true,
    14                     forceStateWhenUnknownKey: entityState);
    15             }
    16         }

    首先我们先看到如果这个对象原先是detach的状态,会由EntityGraph来进行处理,也就是一个图的数据结构,因为这不仅仅是点对点的数据结构,导航属性的存在会让其变成复杂的多对多的有可能的闭环图,我们在进去看一下这里面的操作。

     1      public virtual void AttachGraph(
     2             InternalEntityEntry rootEntry,
     3             EntityState targetState,
     4             EntityState storeGeneratedWithKeySetTargetState,
     5             bool forceStateWhenUnknownKey)
     6             => _graphIterator.TraverseGraph(
     7                 new EntityEntryGraphNode<(EntityState TargetState, EntityState StoreGenTargetState, bool Force)>(
     8                     rootEntry,
     9                     (targetState, storeGeneratedWithKeySetTargetState, forceStateWhenUnknownKey),
    10                     null,
    11                     null),
    12                 PaintAction);

    正如其名所言我们需要遍历这个图,得到所有的导航属性并继续遍历,而遍历的时候的操作我们会给PaintAction 方法去操作,微软正规军写的代码取名非常有意思并且精确,PaintAction告诉我们在遍历的时候做的绘画操作。

    那我们先看一下这个PaintAction 所做的操作,

     1    private static bool PaintAction(
     2             EntityEntryGraphNode<(EntityState TargetState, EntityState StoreGenTargetState, bool Force)> node)
     3         {
     4             SetReferenceLoaded(node);
     5 
     6             var internalEntityEntry = node.GetInfrastructure();
     7             if (internalEntityEntry.EntityState != EntityState.Detached)
     8             {
     9                 return false;
    10             }
    11 
    12             var (targetState, storeGenTargetState, force) = node.NodeState;
    13 
    14             var (isGenerated, isSet) = internalEntityEntry.IsKeySet;
    15 
    16             internalEntityEntry.SetEntityState(
    17                 isSet
    18                     ? (isGenerated ? storeGenTargetState : targetState)
    19                     : EntityState.Added, // Key can only be not-set if it is store-generated
    20                 acceptChanges: true,
    21                 forceStateWhenUnknownKey: force ? (EntityState?)targetState : null);
    22 
    23             return true;
    24         }

    非常精确的说明了到一个新的node会设置其中的状态,并且我们看到EF的一些tips,就是当你add一个root entity的时候,导航属性是否设置主键来判断这个导航属性是add 的还是modify的状态。现在我们要去看一下这个SetEntityState的这个方法,现在事情变得有趣起来,因为这个entity会有新旧状态之分,要从detach状态变成add的状态,并且主键的值应该如何设定,我们先看一下状态的变化会导致哪些东西产生变化。

     1   private void SetEntityState(EntityState oldState, EntityState newState, bool acceptChanges, bool modifyProperties)
     2         {
     3             var entityType = EntityType;
     4 
     5             StateManager.StateChanging(this, newState);
     6 
     7             if (newState == EntityState.Unchanged
     8                 && oldState == EntityState.Modified)
     9             {
    10                 if (acceptChanges)
    11                 {
    12                     _originalValues.AcceptChanges(this);
    13                 }
    14                 else
    15                 {
    16                     _originalValues.RejectChanges(this);
    17                 }
    18             }
    19 
    20             SetServiceProperties(oldState, newState);
    21 
    22             _stateData.EntityState = newState;
    23 
    24             if (oldState == EntityState.Detached)
    25             {
    26                 StateManager.StartTracking(this);
    27             }
    28             else if (newState == EntityState.Detached)
    29             {
    30                 StateManager.StopTracking(this, oldState);
    31             }
    32 
    33             FireStateChanged(oldState);
    34 
    35             if (newState == EntityState.Unchanged)
    36             {
    37                 SharedIdentityEntry?.SetEntityState(EntityState.Detached);
    38             }
    39 
    40             if ((newState == EntityState.Deleted
    41                     || newState == EntityState.Detached)
    42                 && StateManager.CascadeDeleteTiming == CascadeTiming.Immediate)
    43             {
    44                 StateManager.CascadeDelete(this, force: false);
    45             }
    46         }

    截取一些核心代码如上,在第五行的代码中,会将这个entity状态从detach状态变为add状态,也就是将StateManager的五个reference map进行处理,并且触发了这个entity的StateChanging方法,然后在26行代码中如果这个entity的old状态是detach,则需要StateManager去开始追踪他 StateManager.StartTracking(this);,追踪的概念是为这个实体生成唯一的Identity,并为这个实体生成一系列的快照,以后这个实体的所有的变化会与快照进行对比,这个快照会有origin values 和导航属性的快照,这些做完之后这个node就是已经是一个状态成add 的entity并且已经是追踪的状态。

         public virtual void TraverseGraph<TState>(
                EntityEntryGraphNode<TState> node,
                Func<EntityEntryGraphNode<TState>, bool> handleNode)
            {
                if (!handleNode(node))
                {
                    return;
                }
    
                var internalEntityEntry = node.GetInfrastructure();
                var navigations = internalEntityEntry.EntityType.GetNavigations()
                    .Concat<INavigationBase>(internalEntityEntry.EntityType.GetSkipNavigations());
    
                var stateManager = internalEntityEntry.StateManager;
    
                foreach (var navigation in navigations)
                {
                    var navigationValue = internalEntityEntry[navigation];
    
                    if (navigationValue != null)
                    {
                        var targetEntityType = navigation.TargetEntityType;
                        if (navigation.IsCollection)
                        {
                            foreach (var relatedEntity in ((IEnumerable)navigationValue).Cast<object>().ToList())
                            {
                                var targetEntry = stateManager.GetOrCreateEntry(relatedEntity, targetEntityType);
                                TraverseGraph(
                                    (EntityEntryGraphNode<TState>)node.CreateNode(node, targetEntry, navigation),
                                    handleNode);
                            }
                        }
                        else
                        {
                            var targetEntry = stateManager.GetOrCreateEntry(navigationValue, targetEntityType);
                            TraverseGraph(
                                (EntityEntryGraphNode<TState>)node.CreateNode(node, targetEntry, navigation),
                                handleNode);
                        }
                    }
                }
            }

    接下来就是递归去遍历图的方法,对每一个节点都执行paintaction 的方法,改变状态并跟踪他,有兴趣的小伙伴可以去看一下图的遍历,这个时候所有entity的状态已经更新,现在已经到savechang这个操作了。

     1      public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
     2         {
     3             CheckDisposed();
     4 
     5             SavingChanges?.Invoke(this, new SavingChangesEventArgs(acceptAllChangesOnSuccess));
     6 
     7             var interceptionResult = DbContextDependencies.UpdateLogger.SaveChangesStarting(this);
     8 
     9             TryDetectChanges();
    10 
    11             try
    12             {
    13                 var entitiesSaved = interceptionResult.HasResult
    14                     ? interceptionResult.Result
    15                     : DbContextDependencies.StateManager.SaveChanges(acceptAllChangesOnSuccess);
    16 
    17                 var result = DbContextDependencies.UpdateLogger.SaveChangesCompleted(this, entitiesSaved);
    18 
    19                 SavedChanges?.Invoke(this, new SavedChangesEventArgs(acceptAllChangesOnSuccess, result));
    20 
    21                 return result;
    22             }
    23             catch (DbUpdateConcurrencyException exception)
    24             {
    25                 EntityFrameworkEventSource.Log.OptimisticConcurrencyFailure();
    26 
    27                 DbContextDependencies.UpdateLogger.OptimisticConcurrencyException(this, exception);
    28 
    29                 SaveChangesFailed?.Invoke(this, new SaveChangesFailedEventArgs(acceptAllChangesOnSuccess, exception));
    30 
    31                 throw;
    32             }
    33             catch (Exception exception)
    34             {
    35                 DbContextDependencies.UpdateLogger.SaveChangesFailed(this, exception);
    36 
    37                 SaveChangesFailed?.Invoke(this, new SaveChangesFailedEventArgs(acceptAllChangesOnSuccess, exception));
    38 
    39                 throw;
    40             }
    41         }

    在第9行的代码会显示出追踪的功能,一些实体会因为add完之后继续修改状态,这时我们会根据快照进行相应的修改,这个就是changetracker 这个对象去实现的,然后我们就会根据statemanager的五个referencemap去得到需要保存的对象,然后我们将这些entity与相应的状态给到dayabase组件。

     1      protected virtual int SaveChanges([NotNull] IList<IUpdateEntry> entriesToSave)
     2         {
     3             _concurrencyDetector?.EnterCriticalSection();
     4 
     5             try
     6             {
     7                 EntityFrameworkEventSource.Log.SavingChanges();
     8 
     9                 return _database.SaveChanges(entriesToSave);
    10             }
    11             finally
    12             {
    13                 _concurrencyDetector?.ExitCriticalSection();
    14             }
    15         }

    而这个database不管是什么sql server 还是pgsql 等等,这些都是在之前配置文件配置的扩展组件中,因为本例子中使用memory database进行测试,而memory database 就是用内存的字典对象存储的。有兴趣的小伙伴可以自己去看一下实现的原理。

    好了,现在EF core 的代码原理已经结束了,因为这几遍博客完全从代码出发写的比较生硬,希望建议小伙伴自己去clone代码对照这边博客去学习ef 的内部实现方式,图的数据结构确实适合解析导航属性的问题给人一亮的感觉,最后谢谢大家了。如果有任何不解的问题或者指正欢迎留言额。再次谢谢大家的阅读。

  • 相关阅读:
    设计模式学习笔记建造者模式
    ajax应用总结
    带有角色信息的FormsAuthentication身份验证
    jquery.validate中文API和应用实例(一)简单验证
    jquery.validate中文API和应用实例(三)高级验证基础
    设计模式学习笔记抽象工厂模式
    jquery.validate中文API和应用实例(二)简单验证规则的应用
    扩展jquery实现客户端表格的分页、排序
    如何查看修改mysql编码格式
    emacs使用技巧
  • 原文地址:https://www.cnblogs.com/neilhu/p/14534161.html
Copyright © 2011-2022 走看看