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 的内部实现方式,图的数据结构确实适合解析导航属性的问题给人一亮的感觉,最后谢谢大家了。如果有任何不解的问题或者指正欢迎留言额。再次谢谢大家的阅读。

posted @ 2021-03-14 21:04  NeilHu  阅读(1140)  评论(0编辑  收藏  举报