最近在接触DDD+micro service来开发项目,因为EF Core太适合DDD模式需要的ORM设计,所以这篇博客是从代码角度去理解EF core的内部实现,希望大家能从其中学到一些心得体会去更好的写出高质量的代码。

从github 上去下载ef core仓库, 本篇代码的版本是基于tag v5.0.3的,如果大家在以后看见这篇博客,可以在分支上reset 到这个tag对照这边博客,下载完成之后,配置根目录下的global.json成本机已经安装的sdk, runtime 的版本,直接build通过就可以了。下面是代码的目录。

简单的说一下,benchmark 是用来性能测试的,solution是一些自动化部署的配置文件,src是ef core 的核心代码,test 是此项目的单元测试目录,如果你想更深入了解每个模块的实现逻辑,从单元测试出发是一个非常不错的选择。

在用户阶段我们需要做的如下,代码来自 ef 官方文档

  1. 1 using System;
  2. 2 using System.Linq;
  3. 3
  4. 4 namespace EFGetStarted
  5. 5 {
  6. 6 internal class Program
  7. 7 {
  8. 8 private static void Main()
  9. 9 {
  10. 10 using (var db = new BloggingContext())
  11. 11 {
  12. 12 // Note: This sample requires the database to be created before running.
  13. 13
  14. 14 // Create
  15. 15 Console.WriteLine("Inserting a new blog");
  16. 16 db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
  17. 17 db.SaveChanges();
  18. 18
  19. 19 // Read
  20. 20 Console.WriteLine("Querying for a blog");
  21. 21 var blog = db.Blogs
  22. 22 .OrderBy(b => b.BlogId)
  23. 23 .First();
  24. 24
  25. 25 // Update
  26. 26 Console.WriteLine("Updating the blog and adding a post");
  27. 27 blog.Url = "https://devblogs.microsoft.com/dotnet";
  28. 28 blog.Posts.Add(
  29. 29 new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" });
  30. 30 db.SaveChanges();
  31. 31
  32. 32 // Delete
  33. 33 Console.WriteLine("Delete the blog");
  34. 34 db.Remove(blog);
  35. 35 db.SaveChanges();
  36. 36 }
  37. 37 }
  38. 38 }
  39. 39 }

首先我们先看在new DbContext的时候做了哪些的初始化的动作

  1. 1 protected DbContext()
  2. 2 : this(new DbContextOptions<DbContext>())
  3. 3 {
  4. 4 }

这个Context创建了了自己的DbContextOptions,很明显我们需要的一些配置像数据库组件等信息都是需要在DbContextOptions里配置的,我们继续看下去会发现在初始化的时候会创建一个字典对象,正如其名所说的一样这是在维护一些扩展组件。当你想使用不同类型的数据库是引用的library 就是在动态注册这些组件。在本例中,我们使用InMemoryDatabse来测试。  

  1. 1 protected DbContextOptions(
  2. 2 [NotNull] IReadOnlyDictionary<Type, IDbContextOptionsExtension> extensions)
  3. 3 {
  4. 4 Check.NotNull(extensions, nameof(extensions));
  5. 5
  6. 6 _extensions = extensions;
  7. 7 }

在相应的DbContext的继承子类重写OnConfiguring方法就可以使用memory database,配置如下

  1. 1 protected internal override void OnConfiguring(DbContextOptionsBuilder options)
  2. 2 {
  3. 3 options.UseInMemoryDatabase(nameof(BloggingContext));
  4. 4 }

初始化新的option好的时候,这是一个空的配置文件,后面就是对option做的初始化的动作。

  1. 1 public DbContext([NotNull] DbContextOptions options)
  2. 2 {
  3. 3 Check.NotNull(options, nameof(options));
  4. 4
  5. 5 if (!options.ContextType.IsAssignableFrom(GetType()))
  6. 6 {
  7. 7 throw new InvalidOperationException(CoreStrings.NonGenericOptions(GetType().ShortDisplayName()));
  8. 8 }
  9. 9
  10. 10 _options = options;
  11. 11
  12. 12 // This service is not stored in _setInitializer as this may not be the service provider that will be used
  13. 13 // as the internal service provider going forward, because at this time OnConfiguring has not yet been called.
  14. 14 // Mostly that isn't a problem because set initialization is done by our internal services, but in the case
  15. 15 // where some of those services are replaced, this could initialize set using non-replaced services.
  16. 16 // In this rare case if this is a problem for the app, then the app can just not use this mechanism to create
  17. 17 // DbSet instances, and this code becomes a no-op. However, if this set initializer is then saved and used later
  18. 18 // for the Set method, then it makes the problem bigger because now an app is using the non-replaced services
  19. 19 // even when it doesn't need to.
  20. 20 ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: false)
  21. 21 .GetRequiredService<IDbSetInitializer>()
  22. 22 .InitializeSets(this);
  23. 23
  24. 24 EntityFrameworkEventSource.Log.DbContextInitializing();
  25. 25 }

check完之后,ServiceProviderCache的单例模式去获取内部的service provide,这个很明显是对需要的组件进行依赖注入。我们截取了一些核心代码如下。

  1. 1 public virtual IServiceProvider GetOrAdd([NotNull] IDbContextOptions options, bool providerRequired)
  2. 2 {
  3. 3 var key = options.Extensions
  4. 4 .OrderBy(e => e.GetType().Name)
  5. 5 .Aggregate(0L, (t, e) => (t * 397) ^ ((long)e.GetType().GetHashCode() * 397) ^ e.Info.GetServiceProviderHashCode());
  6. 6
  7. 7 return _configurations.GetOrAdd(key, k => BuildServiceProvider()).ServiceProvider;
  8. 8
  9. 9 (IServiceProvider ServiceProvider, IDictionary<string, string> DebugInfo) BuildServiceProvider()
  10. 10 {
  11. 11
  12. 12 var services = new ServiceCollection();
  13. 13 var hasProvider = ApplyServices(options, services);
  14. 14
  15. 15 var serviceProvider = services.BuildServiceProvider();
  16. 16
  17. 17 if (hasProvider)
  18. 18 {
  19. 19 serviceProvider
  20. 20 .GetRequiredService<ISingletonOptionsInitializer>()
  21. 21 .EnsureInitialized(serviceProvider, options);
  22. 22 }
  23. 23
  24. 24 return (serviceProvider, debugInfo);
  25. 25 }
  26. 26 }

从上面可以看出,option 的注册扩展组件不一样会创建不一样的service provide,在第13行代码会进行依赖注入,我们点进去之后会看到如下代码。

  1. 1 private static bool ApplyServices(IDbContextOptions options, ServiceCollection services)
  2. 2 {
  3. 3 var coreServicesAdded = false;
  4. 4
  5. 5 foreach (var extension in options.Extensions)
  6. 6 {
  7. 7 extension.ApplyServices(services);
  8. 8
  9. 9 if (extension.Info.IsDatabaseProvider)
  10. 10 {
  11. 11 coreServicesAdded = true;
  12. 12 }
  13. 13 }
  14. 14
  15. 15 if (coreServicesAdded)
  16. 16 {
  17. 17 return true;
  18. 18 }
  19. 19
  20. 20 new EntityFrameworkServicesBuilder(services).TryAddCoreServices();
  21. 21
  22. 22 return false;
  23. 23 }

循环option获取其中的扩展组件,每个组件会有自己的依赖对象注入,如果没找到database provider,再注入当前的context的核心依赖对象注入,注入的逻辑不用看了,后面去查接口的实现对象参考这个就行了。代码到现在我们能看到已经获取service provider了,接下来继续看InitializeSets的方法。

  1. 1 public virtual void InitializeSets(DbContext context)
  2. 2 {
  3. 3 foreach (var setInfo in _setFinder.FindSets(context.GetType()).Where(p => p.Setter != null))
  4. 4 {
  5. 5 setInfo.Setter.SetClrValue(
  6. 6 context,
  7. 7 ((IDbSetCache)context).GetOrAddSet(_setSource, setInfo.Type));
  8. 8 }
  9. 9 }

我们记得之前声明dbcontext 的时候我们会写dbset 属性,但是并没有对其进行赋值,并且所有对表的操作仿佛都是通过这个对象来完成的,是因为dbcontext 帮我们自动做了赋值的操作,我们找到findSets的实现逻辑。

  1. 1 public virtual IReadOnlyList<DbSetProperty> FindSets(Type contextType)
  2. 2 => _cache.GetOrAdd(contextType, FindSetsNonCached);
  3. 3
  4. 4 private static DbSetProperty[] FindSetsNonCached(Type contextType)
  5. 5 {
  6. 6 var factory = new ClrPropertySetterFactory();
  7. 7
  8. 8 return contextType.GetRuntimeProperties()
  9. 9 .Where(
  10. 10 p => !p.IsStatic()
  11. 11 && !p.GetIndexParameters().Any()
  12. 12 && p.DeclaringType != typeof(DbContext)
  13. 13 && p.PropertyType.GetTypeInfo().IsGenericType
  14. 14 && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
  15. 15 .OrderBy(p => p.Name)
  16. 16 .Select(
  17. 17 p => new DbSetProperty(
  18. 18 p.Name,
  19. 19 p.PropertyType.GenericTypeArguments.Single(),
  20. 20 p.SetMethod == null ? null : factory.Create(p)))
  21. 21 .ToArray();
  22. 22 }

上面的代码会显示不是静态的,不是索引,type不是dbcontext的,是泛型的,是dbset泛型的属性会当做dbcontext 的set来处理,然后map成DbSetProperty对象进行维护,值得注意的一点是SetMethod为factory.Create(p)返回的Func类型,这个时候对象并没有赋值,指到在上面代码setInfo.Setter.SetClrValue调用完这个set才是真正的进行赋值,而其中的逻辑是如下所示。

  1. 1 [UsedImplicitly]
  2. 2 private static Func<DbContext, string, object> CreateSetFactory<TEntity>()
  3. 3 where TEntity : class
  4. 4 => (c, name) => new InternalDbSet<TEntity>(c, name);

其中的dbset 属性就是一个个的InternalDbSet对象,初始化set之后dbcontext就是日志记录一下,这个不是我的研究重点对象,到此为止一个dbcontext对象创建成功,这个时候我们的疑问就来了,我们的数据库配置的组件等配置是什么时候初始化的呢,明显现在的option 是非常干净的。

不用急我们现在来看一下第一个代码片段中的db.Add方法吧,看一下这个里面做了啥。

  1. 1 private EntityEntry<TEntity> SetEntityState<TEntity>(
  2. 2 TEntity entity,
  3. 3 EntityState entityState)
  4. 4 where TEntity : class
  5. 5 {
  6. 6 var entry = EntryWithoutDetectChanges(entity);
  7. 7
  8. 8 SetEntityState(entry.GetInfrastructure(), entityState);
  9. 9
  10. 10 return entry;
  11. 11 }

我们会传入一个entity对象,并且将entityState 置为add 的状态,这个时候我们会涉及到一个重要的对象,就是DbContextDependencies.StateManager,这个就是内部的entity的状态管理对象,我们稍后会讨论这个对象。

  1. 1 private EntityEntry<TEntity> EntryWithoutDetectChanges<TEntity>(TEntity entity)
  2. 2 where TEntity : class
  3. 3 => new(DbContextDependencies.StateManager.GetOrCreateEntry(entity));

这个时候我们需要重视DbContextDependencies这个对象,也就是dbcontext的依赖对象,它会通过InternalServiceProvider对象来获得,但是其中会有一些初始化的逻辑

  1. 1 private IServiceProvider InternalServiceProvider
  2. 2 {
  3. 3 get
  4. 4 {
  5. 5 CheckDisposed();
  6. 6
  7. 7 if (_contextServices != null)
  8. 8 {
  9. 9 return _contextServices.InternalServiceProvider;
  10. 10 }
  11. 11
  12. 12 if (_initializing)
  13. 13 {
  14. 14 throw new InvalidOperationException(CoreStrings.RecursiveOnConfiguring);
  15. 15 }
  16. 16
  17. 17 try
  18. 18 {
  19. 19 _initializing = true;
  20. 20
  21. 21 var optionsBuilder = new DbContextOptionsBuilder(_options);
  22. 22
  23. 23 OnConfiguring(optionsBuilder);
  24. 24
  25. 25 if (_options.IsFrozen
  26. 26 && !ReferenceEquals(_options, optionsBuilder.Options))
  27. 27 {
  28. 28 throw new InvalidOperationException(CoreStrings.PoolingOptionsModified);
  29. 29 }
  30. 30
  31. 31 var options = optionsBuilder.Options;
  32. 32
  33. 33 _serviceScope = ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: true)
  34. 34 .GetRequiredService<IServiceScopeFactory>()
  35. 35 .CreateScope();
  36. 36
  37. 37 var scopedServiceProvider = _serviceScope.ServiceProvider;
  38. 38
  39. 39 var contextServices = scopedServiceProvider.GetService<IDbContextServices>();
  40. 40
  41. 41 contextServices.Initialize(scopedServiceProvider, options, this);
  42. 42
  43. 43 _contextServices = contextServices;
  44. 44
  45. 45 DbContextDependencies.InfrastructureLogger.ContextInitialized(this, options);
  46. 46 }
  47. 47 finally
  48. 48 {
  49. 49 _initializing = false;
  50. 50 }
  51. 51
  52. 52 return _contextServices.InternalServiceProvider;
  53. 53 }
  54. 54 }

在23行的OnConfiguring方法就是会调用我们配置的数据库组件,本例中我们用的就是inmemorydatabase,我们现在撇一下这个组件中间做了啥。很简单的就是在option中注册了扩展组件InMemoryOptionsExtension,之前说过注册了组件之后会重新生成新的server provider, 在新的server collection 重新注入memory database 组件所需要的依赖对象。在扩展组件的InMemoryOptionsExtension.ApplyServices 方法。这是每个扩展组件必须要实现的方法。现在我们知道database组件现在已经注册进来了。继续查看contetx.add 方法的逻辑。

  1. 1 private IServiceProvider InternalServiceProvider
  2. 2 {
  3. 3 get
  4. 4 {
  5. 5 CheckDisposed();
  6. 6
  7. 7 if (_contextServices != null)
  8. 8 {
  9. 9 return _contextServices.InternalServiceProvider;
  10. 10 }
  11. 11
  12. 12 if (_initializing)
  13. 13 {
  14. 14 throw new InvalidOperationException(CoreStrings.RecursiveOnConfiguring);
  15. 15 }
  16. 16
  17. 17 try
  18. 18 {
  19. 19 _initializing = true;
  20. 20
  21. 21 var optionsBuilder = new DbContextOptionsBuilder(_options);
  22. 22
  23. 23 OnConfiguring(optionsBuilder);
  24. 24
  25. 25 if (_options.IsFrozen
  26. 26 && !ReferenceEquals(_options, optionsBuilder.Options))
  27. 27 {
  28. 28 throw new InvalidOperationException(CoreStrings.PoolingOptionsModified);
  29. 29 }
  30. 30
  31. 31 var options = optionsBuilder.Options;
  32. 32
  33. 33 _serviceScope = ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: true)
  34. 34 .GetRequiredService<IServiceScopeFactory>()
  35. 35 .CreateScope();
  36. 36
  37. 37 var scopedServiceProvider = _serviceScope.ServiceProvider;
  38. 38
  39. 39 var contextServices = scopedServiceProvider.GetService<IDbContextServices>();
  40. 40
  41. 41 contextServices.Initialize(scopedServiceProvider, options, this);
  42. 42
  43. 43 _contextServices = contextServices;
  44. 44
  45. 45 DbContextDependencies.InfrastructureLogger.ContextInitialized(this, options);
  46. 46 }
  47. 47 finally
  48. 48 {
  49. 49 _initializing = false;
  50. 50 }
  51. 51
  52. 52 return _contextServices.InternalServiceProvider;
  53. 53 }
  54. 54 }

现在转到上上上个代码片段,statemanager 需要创建一个entity,这时候会判断这个entity存在不存在,如果不存在会在facyor 方法创建一个statemanager管理的entity,然后在statemanager更新这个entity的状态。

  1. 1 public virtual InternalEntityEntry GetOrCreateEntry(object entity)
  2. 2 {
  3. 3 var entry = TryGetEntry(entity);
  4. 4 if (entry == null)
  5. 5 {
  6. 6 var entityType = _model.FindRuntimeEntityType(entity.GetType());
  7. 7 if (entityType == null)
  8. 8 {
  9. 9 if (_model.IsShared(entity.GetType()))
  10. 10 {
  11. 11 throw new InvalidOperationException(
  12. 12 CoreStrings.UntrackedDependentEntity(
  13. 13 entity.GetType().ShortDisplayName(),
  14. 14 "." + nameof(EntityEntry.Reference) + "()." + nameof(ReferenceEntry.TargetEntry),
  15. 15 "." + nameof(EntityEntry.Collection) + "()." + nameof(CollectionEntry.FindEntry) + "()"));
  16. 16 }
  17. 17
  18. 18 throw new InvalidOperationException(CoreStrings.EntityTypeNotFound(entity.GetType().ShortDisplayName()));
  19. 19 }
  20. 20
  21. 21 if (entityType.FindPrimaryKey() == null)
  22. 22 {
  23. 23 throw new InvalidOperationException(CoreStrings.KeylessTypeTracked(entityType.DisplayName()));
  24. 24 }
  25. 25
  26. 26 entry = _internalEntityEntryFactory.Create(this, entityType, entity);
  27. 27
  28. 28 UpdateReferenceMaps(entry, EntityState.Detached, null);
  29. 29 }
  30. 30
  31. 31 return entry;
  32. 32 }

statemanager会有五个对象分别记录每一个entity的不同的记录,如下图所示

  1. 1 switch (state)
  2. 2 {
  3. 3 case EntityState.Detached:
  4. 4 _detachedReferenceMap ??= new Dictionary<object, InternalEntityEntry>(LegacyReferenceEqualityComparer.Instance);
  5. 5 _detachedReferenceMap[mapKey] = entry;
  6. 6 break;
  7. 7 case EntityState.Unchanged:
  8. 8 _unchangedReferenceMap ??=
  9. 9 new Dictionary<object, InternalEntityEntry>(LegacyReferenceEqualityComparer.Instance);
  10. 10 _unchangedReferenceMap[mapKey] = entry;
  11. 11 break;
  12. 12 case EntityState.Deleted:
  13. 13 _deletedReferenceMap ??= new Dictionary<object, InternalEntityEntry>(LegacyReferenceEqualityComparer.Instance);
  14. 14 _deletedReferenceMap[mapKey] = entry;
  15. 15 break;
  16. 16 case EntityState.Modified:
  17. 17 _modifiedReferenceMap ??= new Dictionary<object, InternalEntityEntry>(LegacyReferenceEqualityComparer.Instance);
  18. 18 _modifiedReferenceMap[mapKey] = entry;
  19. 19 break;
  20. 20 case EntityState.Added:
  21. 21 _addedReferenceMap ??= new Dictionary<object, InternalEntityEntry>(LegacyReferenceEqualityComparer.Instance);
  22. 22 _addedReferenceMap[mapKey] = entry;
  23. 23 break;
  24. 24 }

随后在这个entity会进入到一个changetracking 的状态。

好了今天写到这个地方了,进入tracking 的状态时会有一个graph 对象所管理,其中拥有一些图的数据结构,现在快11点了,就写到这里后面我会跟上后续的内容,谢谢大家的阅读,如果有任何不理解或者指正的地方欢迎评论,最后谢谢大家。

EF Core 源码分析的更多相关文章

  1. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  2. ASP.NET Core[源码分析篇] - WebHost

    _configureServicesDelegates的承接 在[ASP.NET Core[源码分析篇] - Startup]这篇文章中,我们得知了目前为止(UseStartup),所有的动作都是在_ ...

  3. ASP.NET Core[源码分析篇] - Authentication认证

    原文:ASP.NET Core[源码分析篇] - Authentication认证 追本溯源,从使用开始 首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务 ...

  4. DOTNET CORE源码分析之IOC容器结果获取内容补充

    补充一下ServiceProvider的内容 可能上一篇文章DOTNET CORE源码分析之IServiceProvider.ServiceProvider.IServiceProviderEngin ...

  5. [asp.net core 源码分析] 01 - Session

    1.Session文档介绍 毋庸置疑学习.Net core最好的方法之一就是学习微软.Net core的官方文档:https://docs.microsoft.com/zh-cn/aspnet/cor ...

  6. ASP.NET Core[源码分析篇] - 认证

    追本溯源,从使用开始 首先看一下我们的通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务,这里通过JWT的认证方式讲解 public void ConfigureServ ...

  7. ASP.NET Core[源码分析篇] - Startup

    应用启动的重要类 - Startup 在ASP.NET Core - 从Program和Startup开始这篇文章里面,我们知道了Startup这个类的重要性,它主要负责了: 配置应用需要的服务(服务 ...

  8. DOTNET CORE源码分析之ServiceDescriptor

    ServiceDescriptor在.net core中的作用就是DI中注入服务元素的描述.每一个元素核心内容部分包括需要注入的服务元素的类型ServiceType,它对应的接口(如果有的话)Impl ...

  9. DOTNET CORE源码分析之IServiceProvider、ServiceProvider、IServiceProviderEngine、ServiceProviderEngine和ServiceProviderEngineScope

    首先谈一下IServiceProvider IServiceProvider只提供给了一个根据类型获取对象的功能,试想一下IOC总得有一个找到对象,具体如下 public interface ISer ...

随机推荐

  1. docker07-数据存储

    Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式: 数据卷(Volumes) 挂载主机目录 (Bind mounts) 数据卷 是一个可供一个或多个容器使用的特殊目录,它绕过 U ...

  2. Next.js SSR Tutorials

    Next.js SSR Tutorials https://codesandbox.io/s/nextjs-demo-h49zt cli $ npx create-next-app ssr-demo- ...

  3. 如何使用 Python 编写后端 API 接口

    如何使用 Python 编写后端 API 接口 get API Python3 # coding:utf-8 import json # ModuleNotFoundError: No module ...

  4. CDN 工作原理剖析

    CDN 工作原理剖析 CDN / Content Delivery Network / 内容分发网络 https://www.cloudflare.com/zh-cn/learning/cdn/wha ...

  5. RESTful 架构 && RESTful API

    RESTful 架构 && RESTful API REpresentational State Transfer (REST) 具象状态传输https://en.wikipedia. ...

  6. PostgreSQL All In One

    PostgreSQL All In One SQL macOS https://www.postgresql.org/download/macosx/ EDB installer PostgreSQL ...

  7. git cli all in one

    git cli all in one https://www.atlassian.com/git/tutorials/learn-git-with-bitbucket-cloud git create ...

  8. 为什么 Python 的 f-string 可以连接字符串与数字?

    本文出自"Python为什么"系列,归档在 Github 上:https://github.com/chinesehuazhou/python-whydo 毫无疑问,Python ...

  9. JDK环境解析,安装和目的

    目录 1. JDK环境解析 1.1 JVM 1.2 JRE 1.3 JDK 2. JDK安装 2.1 为什么使用JDK8 2.1.1 更新 2.1.2 稳定 2.1.3 需求 2.2 安装JDK 2. ...

  10. redis缓存穿透穿透解决方案-布隆过滤器

    redis缓存穿透穿透解决方案-布隆过滤器 我们先来看一段代码 cache_key = "id:1" cache_value = GetValueFromRedis(cache_k ...