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