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