EF Core 原理從原始碼出發(二)

NeilHu發表於2021-03-14

緊接著我的上一篇部落格,可以點選這裡回到上一篇部落格,上回分析到ef 一個重要的物件,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 的內部實現方式,圖的資料結構確實適合解析導航屬性的問題給人一亮的感覺,最後謝謝大家了。如果有任何不解的問題或者指正歡迎留言額。再次謝謝大家的閱讀。

相關文章