• EF Core 原理从源码出发(一)


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

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

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

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

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

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

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

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

     1   public DbContext([NotNull] DbContextOptions options)
     2         {
     3             Check.NotNull(options, nameof(options));
     4 
     5             if (!options.ContextType.IsAssignableFrom(GetType()))
     6             {
     7                 throw new InvalidOperationException(CoreStrings.NonGenericOptions(GetType().ShortDisplayName()));
     8             }
     9 
    10             _options = options;
    11 
    20             ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: false)
    21                 .GetRequiredService<IDbSetInitializer>()
    22                 .InitializeSets(this);
    23 
    24             EntityFrameworkEventSource.Log.DbContextInitializing();
    25         }

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

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

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

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

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

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

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

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

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

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

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

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

     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         }

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

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

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

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

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

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

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

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

    statemanager会有五个对象分别记录每一个entity的不同的记录,这五个对象分别是_detachedReferenceMap,_unchangedReferenceMap,_deletedReferenceMap,_modifiedReferenceMap,_addedReferenceMap,对应着放弃追踪,没有变化,删除的,修改的,增加的各种entity 对象,如下图所示,就是我们EF在内存维护的给个状态的对象。

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

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

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

  • 相关阅读:
    ajax优缺点及用法
    通俗易懂:窗口函数 | 全是案例
    mysql基本数据类型
    Elasticsearch从入门到专家
    shell数组实和变量扩展实现keyvalue设计
    catboost学习笔记
    pacman总结
    常用archlinux软件索引
    Linux配置打印机
    记录一次实战GetShell
  • 原文地址:https://www.cnblogs.com/neilhu/p/14529810.html
Copyright © 2020-2023  润新知