EF Core 原理从源码出发(二)

2024-04-10 12:32
文章标签 源码 原理 出发 core ef

本文主要是介绍EF Core 原理从源码出发(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

复制代码
1 private EntityEntry SetEntityState(
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(
EntityEntryGraphNode node,
Func<EntityEntryGraphNode, 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 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 }
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com

这篇关于EF Core 原理从源码出发(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/891067

相关文章

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

Golang HashMap实现原理解析

《GolangHashMap实现原理解析》HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持高效的插入、查找和删除操作,:本文主要介绍GolangH... 目录HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

鸿蒙中@State的原理使用详解(HarmonyOS 5)

《鸿蒙中@State的原理使用详解(HarmonyOS5)》@State是HarmonyOSArkTS框架中用于管理组件状态的核心装饰器,其核心作用是实现数据驱动UI的响应式编程模式,本文给大家介绍... 目录一、@State在鸿蒙中是做什么的?二、@Spythontate的基本原理1. 依赖关系的收集2.

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、