【原始碼解讀(一)】EFCORE原始碼解讀之建立DBContext查詢攔截

2023-10-13 18:01:42

引言

    在網上很少看到有關於系統講解EFCore原始碼的,可能大概也許是因為EFCore的原始碼總體是沒有asp.net web的原始碼流程清晰,正如群友所說,EFCore的原始碼大致看起來有點凌亂,與其說凌亂,不如說是沒有一個好的方向;然後昨天在群裡有一個朋友再說,EfCore的攔截器如何注入Web的服務,以及EfCore如何自定義查詢,我就看了一下EfCore的原始碼,在此之前我針對asp.net web 做了一個原始碼解讀,有興趣的朋友可以看前面的文章,也給別人說過啥時候講解一下efcore的原始碼,剛好藉助這麼一個機會,講一講EfCore的原始碼,本篇文章作為一個開端,會呈現一下幾點

    一:首先是AddDbContext裡面做了什麼。

    二:DbContext的建構函式裡面做了那些事情。

    三:如何在EfCore的服務中獲取到Web注入的服務的方式之一。

    四:攔截查詢的幾種方式。

    五:使用快取查詢方法提升效能。

    六:如何託管EFCORE的IOC容器(和Web的IOC使用同一個)

    以上作為本篇文章的所有內容,接下來,我們來開始講解原始碼,動手實踐。

AddDbContext

    EfCore提供了AddDbContext,AddDbContextFactory,AddDbContextPool,AddPooledDbContextFactory這幾種擴充套件方法,我們會依次講解,首先會講解AddDbContext,後續的文章會依次講解其餘的方法。話不多說,上原始碼。下面是AddDbContext的原始碼,提供了多種方法,但是最終都會呼叫到這裡,第一個引數是一個設定OptionBuilder的委託,傳入了ServiceProvider和OptionBuilder,第二,三個分別是DbContext和DBContextOption的生命週期。

    在下面的程式碼,剛開始判斷了如果DBContext的生命週期是單例,要將Option的生命週期也設定為單例,如果不設定為單例,就會出現錯誤,這個錯誤在之前講解IOC的文章中,我記得也提到過,接下來判斷設定Option的委託是否為null,如果不為null,那DBContext的建構函式是必須要有一個引數,所以下面呼叫了一個方法CheckContextConstructors。

 public static IServiceCollection AddDbContext<TContextService, TContextImplementation>(
     this IServiceCollection serviceCollection,
     Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction,
     ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
     ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
     where TContextImplementation : DbContext, TContextService
 {
     if (contextLifetime == ServiceLifetime.Singleton)
     {
         optionsLifetime = ServiceLifetime.Singleton;
     }

     if (optionsAction != null)
     {
         CheckContextConstructors<TContextImplementation>();
     }

     AddCoreServices<TContextImplementation>(serviceCollection, optionsAction, optionsLifetime);

     if (serviceCollection.Any(d => d.ServiceType == typeof(IDbContextFactorySource<TContextImplementation>)))
     {
         // Override registration made by AddDbContextFactory
         var serviceDescriptor = serviceCollection.FirstOrDefault(d => d.ServiceType == typeof(TContextImplementation));
         if (serviceDescriptor != null)
         {
             serviceCollection.Remove(serviceDescriptor);
         }
     }

     serviceCollection.TryAdd(new ServiceDescriptor(typeof(TContextService), typeof(TContextImplementation), contextLifetime));

     if (typeof(TContextService) != typeof(TContextImplementation))
     {
         serviceCollection.TryAdd(
             new ServiceDescriptor(
                 typeof(TContextImplementation),
                 p => (TContextImplementation)p.GetService<TContextService>()!,
                 contextLifetime));
     }

     return serviceCollection;
 }

 private static void CheckContextConstructors<TContext>()
     where TContext : DbContext
 {
     var declaredConstructors = typeof(TContext).GetTypeInfo().DeclaredConstructors.ToList();
     if (declaredConstructors.Count == 1
         && declaredConstructors[0].GetParameters().Length == 0)
     {
         throw new ArgumentException(CoreStrings.DbContextMissingConstructor(typeof(TContext).ShortDisplayName()));
     }
 }

    在CheckContextConstructors,我們看到反射去獲取DBContext的繼承類,查詢建構函式,並且引數如果是0就會報異常。接下來在往下走,呼叫了一個AddCoreServices的方法,在這個方法裡,我們是將DBContextOptions的泛型和非泛型注入到容器裡面去,其中有一個CreateDbContextOptions的方法,裡面去new了一個DbContextOptionsBuilder類,然後呼叫了一個UseApplicationServiceProvider方法,

    private static void AddCoreServices<TContextImplementation>(
        IServiceCollection serviceCollection,
        Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction,
        ServiceLifetime optionsLifetime)
        where TContextImplementation : DbContext
    {
        serviceCollection.TryAdd(
            new ServiceDescriptor(
                typeof(DbContextOptions<TContextImplementation>),
                p => CreateDbContextOptions<TContextImplementation>(p, optionsAction),
                optionsLifetime));

        serviceCollection.Add(
            new ServiceDescriptor(
                typeof(DbContextOptions),
                p => p.GetRequiredService<DbContextOptions<TContextImplementation>>(),
                optionsLifetime));
    }

    private static DbContextOptions<TContext> CreateDbContextOptions<TContext>(
        IServiceProvider applicationServiceProvider,
        Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction)
        where TContext : DbContext
    {
        var builder = new DbContextOptionsBuilder<TContext>(
            new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>()));

        builder.UseApplicationServiceProvider(applicationServiceProvider);

        optionsAction?.Invoke(applicationServiceProvider, builder);

        return builder.Options;
    }

    下面是一個UseApplicationServiceProvider的呼叫鏈,最終呼叫到了WithApplicationServiceProvider方法,可以看到是返回了一個CoreOptionsExtension,最終呼叫WithOptions新增到了DbContextOptionsBuilder裡面去,上面的程式碼中,我們new了一個DbContextOptionsBuilder,裡面傳入了一個DbContextOptions,建構函式傳入了new Dictionary<Type,IDbContextOptionsExtension>(),最終我們的CoreOptionsExtension會新增到我們傳入的這個字典裡,用來儲存所有的IDbContextOptionsExtension,這個介面可以理解為,資料庫Options的擴充套件,介面定義如下,Info是關於擴充套件的一些後設資料資訊,ApplyService方法,引數是一個IServiceCollection,這個方法是我們將我們要注入的服務注入到這個裡面去,因為EfCore的IOC和Web的IOC是區分開的,所以使用了不同的IServiceCollection,雖然提供了UseApplicationServiceProvider和UseInternalServiceProvider方法,實際上並不能實現IOC接管,設計實在是雞肋,待會到了DbContext的建構函式中我們會看到為什麼說雞肋。Validate方法則是驗證當前擴充套件,例如你得這個實現裡面有一些引數,是必須要設定,或者設定有一個規則,我們在這裡驗證我們的設定或者規則是否符合我們需要的資料,如果不符合,在這裡可以直接丟擲異常。

    回到CreateDbContextOptions,此時我們可以確保我們的Option的Extension裡面是有一個CoreOptionsExtension,接下來,判斷有沒有設定OptionsBuilder的委託,呼叫然後返回到AddDbContext。

 public new virtual DbContextOptionsBuilder<TContext> UseApplicationServiceProvider(IServiceProvider? serviceProvider)
     => (DbContextOptionsBuilder<TContext>)base.UseApplicationServiceProvider(serviceProvider);
  public virtual DbContextOptionsBuilder UseApplicationServiceProvider(IServiceProvider? serviceProvider)
      => WithOption(e => e.WithApplicationServiceProvider(serviceProvider));
    public virtual CoreOptionsExtension WithApplicationServiceProvider(IServiceProvider? applicationServiceProvider)
    {
        var clone = Clone();

        clone._applicationServiceProvider = applicationServiceProvider;

        return clone;
    }
  private DbContextOptionsBuilder WithOption(Func<CoreOptionsExtension, CoreOptionsExtension> withFunc)
  {
      ((IDbContextOptionsBuilderInfrastructure)this).AddOrUpdateExtension(
          withFunc(Options.FindExtension<CoreOptionsExtension>() ?? new CoreOptionsExtension()));

      return this;
  }
public interface IDbContextOptionsExtension
{
    /// <summary>
    ///     Information/metadata about the extension.
    /// </summary>
    DbContextOptionsExtensionInfo Info { get; }

    /// <summary>
    ///     Adds the services required to make the selected options work. This is used when there
    ///     is no external <see cref="IServiceProvider" /> and EF is maintaining its own service
    ///     provider internally. This allows database providers (and other extensions) to register their
    ///     required services when EF is creating an service provider.
    /// </summary>
    /// <param name="services">The collection to add services to.</param>
    void ApplyServices(IServiceCollection services);

    /// <summary>
    ///     Gives the extension a chance to validate that all options in the extension are valid.
    ///     Most extensions do not have invalid combinations and so this will be a no-op.
    ///     If options are invalid, then an exception should be thrown.
    /// </summary>
    /// <param name="options">The options being validated.</param>
    void Validate(IDbContextOptions options);
}
    void IDbContextOptionsBuilderInfrastructure.AddOrUpdateExtension<TExtension>(TExtension extension)
        => _options = _options.WithExtension(extension);

    在AddDbContext的最後,這幾行程式碼,是將我們的DbContext注入到我們的IOC容器中去。

if (serviceCollection.Any(d => d.ServiceType == typeof(IDbContextFactorySource<TContextImplementation>)))
{
    // Override registration made by AddDbContextFactory
    var serviceDescriptor = serviceCollection.FirstOrDefault(d => d.ServiceType == typeof(TContextImplementation));
    if (serviceDescriptor != null)
    {
        serviceCollection.Remove(serviceDescriptor);
    }
}

serviceCollection.TryAdd(new ServiceDescriptor(typeof(TContextService), typeof(TContextImplementation), contextLifetime));

if (typeof(TContextService) != typeof(TContextImplementation))
{
    serviceCollection.TryAdd(
        new ServiceDescriptor(
            typeof(TContextImplementation),
            p => (TContextImplementation)p.GetService<TContextService>()!,
            contextLifetime));
}

    至此,關於AddDbContext的原始碼講解完畢,接下來進到DbContext建構函式的原始碼講解,這裡設計內容稍微多一些。

DBContext構造

    建構函式的程式碼是整體是沒多少的,但是最重要的還是在於GetOrAdd的方法。所以這裡我會只講這個方法的內部

 public DbContext(DbContextOptions options)
 {
     Check.NotNull(options, nameof(options));

     if (!options.ContextType.IsAssignableFrom(GetType()))
     {
         throw new InvalidOperationException(CoreStrings.NonGenericOptions(GetType().ShortDisplayName()));
     }

     _options = options;

     // This service is not stored in _setInitializer as this may not be the service provider that will be used
     // as the internal service provider going forward, because at this time OnConfiguring has not yet been called.
     // Mostly that isn't a problem because set initialization is done by our internal services, but in the case
     // where some of those services are replaced, this could initialize set using non-replaced services.
     // In this rare case if this is a problem for the app, then the app can just not use this mechanism to create
     // DbSet instances, and this code becomes a no-op. However, if this set initializer is then saved and used later
     // for the Set method, then it makes the problem bigger because now an app is using the non-replaced services
     // even when it doesn't need to.
     ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: false)
         .GetRequiredService<IDbSetInitializer>()
         .InitializeSets(this);

     EntityFrameworkEventSource.Log.DbContextInitializing();
 }

    下面是ServiceProviderCache的全部原始碼,在GetOrAdd方法,先獲取了CoreOptionsExtension,這個我們在AddDbContext的時候,已經新增過了,並且設定了ApplicationProvider,在往下走,判斷InternalServiceProvider,這裡我們沒有設定,就會繼續往下走,判斷了一個ServiceProviderCachingEnabled 這個預設是true,然後下面獲取CoreOptionsExtension的ApplicationServiceProvider,還記得我們在最初的時候AddDbContext的時候,建立DbContextOptionsBuilder的時候,我們UseApplicationServiceProvider方法,就是設定了一個公用的Provider,現在把他在設定為null,如果說這個Provider有啥作用,哈哈哈哈,我認為他就是建立Options的時候需要用,然後給一個有東西的不為空的CoreOptionsExtension,這個方法,實際上我覺得微軟設定為internal最好了,這樣可能會存在誤解開發者,而InternalServiceProvider是很有用,可以和我們的web共用一個ioc容器,在本文的最後,我會將ef的ioc容器託管到web的。

    ServiceProviderCachingEnabled引數代表是否將GetOrAdd通過BuildServiceProvider建立的ServiceProvider快取到_configuration中去,不快取的話,每次都是一個新的Provider,對效能有影響,如果快取,則第一次建立後面都是從那裡面取。

    接下來就到了GetOrAdd最後,要呼叫BuildServiceProvider方法來建立一個ServiceProvider,下面的方法,我們著重看幾個關鍵點一個是ApplyService,一個是new ServiceCollection(說明web的ioc和ef的ioc不是同一個),ReplaceService。

public class ServiceProviderCache
{
    private readonly ConcurrentDictionary<IDbContextOptions, (IServiceProvider ServiceProvider, IDictionary<string, string> DebugInfo)>
        _configurations = new();

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public static ServiceProviderCache Instance { get; } = new();

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual IServiceProvider GetOrAdd(IDbContextOptions options, bool providerRequired)
    {
        var coreOptionsExtension = options.FindExtension<CoreOptionsExtension>();
        var internalServiceProvider = coreOptionsExtension?.InternalServiceProvider;
        if (internalServiceProvider != null)
        {
            ValidateOptions(options);

            var optionsInitializer = internalServiceProvider.GetService<ISingletonOptionsInitializer>();
            if (optionsInitializer == null)
            {
                throw new InvalidOperationException(CoreStrings.NoEfServices);
            }

            if (providerRequired)
            {
                optionsInitializer.EnsureInitialized(internalServiceProvider, options);
            }

            return internalServiceProvider;
        }

        if (coreOptionsExtension?.ServiceProviderCachingEnabled == false)
        {
            return BuildServiceProvider(options, (_configurations, options)).ServiceProvider;
        }

        var cacheKey = options;
        var extension = options.FindExtension<CoreOptionsExtension>();
        if (extension?.ApplicationServiceProvider != null)
        {
            cacheKey = ((DbContextOptions)options).WithExtension(extension.WithApplicationServiceProvider(null));
        }

        return _configurations.GetOrAdd(
                cacheKey,
                static (contextOptions, tuples) => BuildServiceProvider(contextOptions, tuples), (_configurations, options))
            .ServiceProvider;

        static (IServiceProvider ServiceProvider, IDictionary<string, string> DebugInfo) BuildServiceProvider(
            IDbContextOptions _,
            (ConcurrentDictionary<IDbContextOptions, (IServiceProvider ServiceProvider, IDictionary<string, string> DebugInfo)>,
                IDbContextOptions) arguments)
        {
            var (configurations, options) = arguments;

            ValidateOptions(options);

            var debugInfo = new Dictionary<string, string>();
            foreach (var optionsExtension in options.Extensions)
            {
                optionsExtension.Info.PopulateDebugInfo(debugInfo);
            }

            debugInfo = debugInfo.OrderBy(_ => debugInfo.Keys).ToDictionary(d => d.Key, v => v.Value);

            var services = new ServiceCollection();
            var hasProvider = ApplyServices(options, services);

            var replacedServices = options.FindExtension<CoreOptionsExtension>()?.ReplacedServices;
            if (replacedServices != null)
            {
                var updatedServices = new ServiceCollection();
                foreach (var descriptor in services)
                {
                    if (replacedServices.TryGetValue((descriptor.ServiceType, descriptor.ImplementationType), out var replacementType))
                    {
                        ((IList<ServiceDescriptor>)updatedServices).Add(
                            new ServiceDescriptor(descriptor.ServiceType, replacementType, descriptor.Lifetime));
                    }
                    else if (replacedServices.TryGetValue((descriptor.ServiceType, null), out replacementType))
                    {
                        ((IList<ServiceDescriptor>)updatedServices).Add(
                            new ServiceDescriptor(descriptor.ServiceType, replacementType, descriptor.Lifetime));
                    }
                    else
                    {
                        ((IList<ServiceDescriptor>)updatedServices).Add(descriptor);
                    }
                }

                services = updatedServices;
            }

            var serviceProvider = services.BuildServiceProvider();

            if (hasProvider)
            {
                serviceProvider
                    .GetRequiredService<ISingletonOptionsInitializer>()
                    .EnsureInitialized(serviceProvider, options);
            }

            using (var scope = serviceProvider.CreateScope())
            {
                var scopedProvider = scope.ServiceProvider;

                // If loggingDefinitions is null, then there is no provider yet
                var loggingDefinitions = scopedProvider.GetService<LoggingDefinitions>();
                if (loggingDefinitions != null)
                {
                    // Because IDbContextOptions cannot yet be resolved from the internal provider
                    var logger = new DiagnosticsLogger<DbLoggerCategory.Infrastructure>(
                        ScopedLoggerFactory.Create(scopedProvider, options),
                        scopedProvider.GetRequiredService<ILoggingOptions>(),
                        scopedProvider.GetRequiredService<DiagnosticSource>(),
                        loggingDefinitions,
                        new NullDbContextLogger());

                    if (configurations.IsEmpty)
                    {
                        logger.ServiceProviderCreated(serviceProvider);
                    }
                    else
                    {
                        logger.ServiceProviderDebugInfo(
                            debugInfo,
                            configurations.Values.Select(v => v.DebugInfo).ToList());

                        if (configurations.Count >= 20)
                        {
                            logger.ManyServiceProvidersCreatedWarning(
                                configurations.Values.Select(e => e.ServiceProvider).ToList());
                        }
                    }

                    var applicationServiceProvider = options.FindExtension<CoreOptionsExtension>()?.ApplicationServiceProvider;
                    if (applicationServiceProvider?.GetService<IRegisteredServices>() != null)
                    {
                        logger.RedundantAddServicesCallWarning(serviceProvider);
                    }
                }
            }

            return (serviceProvider, debugInfo);
        }
    }

    private static void ValidateOptions(IDbContextOptions options)
    {
        foreach (var extension in options.Extensions)
        {
            extension.Validate(options);
        }
    }

    private static bool ApplyServices(IDbContextOptions options, ServiceCollection services)
    {
        var coreServicesAdded = false;

        foreach (var extension in options.Extensions)
        {
            extension.ApplyServices(services);

            if (extension.Info.IsDatabaseProvider)
            {
                coreServicesAdded = true;
            }
        }

        if (coreServicesAdded)
        {
            return true;
        }

        new EntityFrameworkServicesBuilder(services).TryAddCoreServices();

        return false;
    }
}

     我們先看ApplyService,在這個方法我們看到,是傳入了Options,ServiceCollection,然後迴圈遍歷Options的Extensions,呼叫他的ApplyService方法,傳入serviceCollection,下圖,我們看到預設是有這麼多的實現,根據你在對DBContextOptionsBuilder提供的方法的使用,會給Options的Extensions新增不同的Extension,最後呼叫各自的ApplyService,我們找一個看看具體在做什麼事情。哈哈,不用猜,大家也知道,肯定是注入服務,通過options的Extensions附加一些其他關於EF的功能,並且將他們所需要的服務注入到傳入的ServiceCollection裡面,其他的都是一樣,所以在我們沒有託管ef的ioc到web的時候可以使用這種方式來實現,後面也會寫一個這樣的例子。最後呼叫一下TryAddCoreService方法,這個方法有許多EF需用到重要的服務的注入。

    而ReplaceService就是我們在呼叫DbContextOptionsBuilder的ReplaceService<>方法的時候裡面儲存的我們要替換的型別,以及實現,在這裡重新注入到容器裡,用上面的程式碼結合看,就是ApplyService先注入一遍,然後在替換一下,最後呼叫一下BuildServiceProvider方法生成一個ServiceProvider,在後面的程式碼就是一些紀錄檔相關的設定,此處就不過多講解。

    public virtual void ApplyServices(IServiceCollection services)
        => services.AddEntityFrameworkProxies();

 

 
    在上面的講解中,我們有幾個可以自定義的點就是一個是IDbContextOptionsExtension,這個我們可以在不託管ef的ioc到web的ioc的時候,我們可以實現一個這個介面,然後在程式碼新增到Extension就可以注入EF所需要用到的服務。接下來在下面段落,我會寫一個簡單的例子來注入我們要的服務。(不託管ioc到web的方式)。

EFCore服務注入

    先上程式碼,程式碼沒有多少,就是實現這個介面,定義一個Inject特性,用來標記從Web的IOC我們需要檢索那些介面注入到EF的ioc中去,這樣做有一個弊端就是Web的會注入一遍,Ef也會注入一遍,重複注入,在Program.cs裡面我們先注入一個返回IServiceCollection的Func,這樣在DBContext可以獲取到這個 傳到ServiceExtension裡面,就可以拿到Web的IOC注入的服務。

    builder.Services.AddScoped<IWebGetName, WebGetName>();
    builder.Services.AddSingleton(() => builder.Services);
  [InjectAttribute]
  public interface IWebGetName
  {
      public string GetName();
  }
 [AttributeUsage(AttributeTargets.Interface| AttributeTargets.Class)]
 public class InjectAttribute:Attribute
 {
     public InjectAttribute()
     {
             
     }
 }

 

public class ServiceExtension : IDbContextOptionsExtension
{
    public ServiceExtension(Func<IServiceCollection> func)
    {
        Func = func;
    }
    public DbContextOptionsExtensionInfo Info => new ExtensionInfo(this);

    public Func<IServiceCollection> Func { get; }

    public void ApplyServices(IServiceCollection services)
    {
        var ser=Func();
        var type = ser.Where(s => s.ServiceType?.GetCustomAttribute<InjectAttribute>() != null || s.ImplementationType?.GetCustomAttribute<InjectAttribute>() != null).ToList();
        foreach (var item in type)
        {
            services.TryAdd(new ServiceDescriptor(item.ServiceType, item.ImplementationType, item.Lifetime));
        }
        services.AddScoped<IDBGetName, DBGetName>();
    }

    public void Validate(IDbContextOptions options)
    {
    }
}
 public class ExtensionInfo: DbContextOptionsExtensionInfo
 {
     public ExtensionInfo(IDbContextOptionsExtension extension):base(extension) 
     {
             
     }
     public override bool IsDatabaseProvider => false;

     public override string LogFragment => string.Empty;

     public override int GetServiceProviderHashCode()
     {
         return 0;
     }

     public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
     {
     }

     public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
     {
         return true;
     }
 }

    介面定義好之後,服務也注入進去,接下來就是在DBContext裡面新增這個擴充套件,因為optionsBuilder.Options唯讀,所以我們新增Extension就需要用AddOrUpdateExtension的方法來新增,因為Options在DbContextOptionsBuilder的內部欄位是可以更改的。接下來擴充套件新增進去之後,我們執行程式,獲取一個DBContext,然後就會走到這裡新增我們的擴充套件,從而注入我們注入的IWebGetName,就可以在EF的IOC獲取我們web注入服務。

  builder.Services.AddDbContext<DecodeMicroMsgContext>((a, m) => {

      ((IDbContextOptionsBuilderInfrastructure)m).AddOrUpdateExtension(new ServiceExtension(a.GetService<Func<IServiceCollection>>()));
      m.UseSqlite("Data Source=C:\\Users\\Chenxd\\Desktop\\資料\\CrackMsg\\CrackDb\\CrackDb\\bin\\Debug\\net7.0-windows\\TOOLS\\output\\decode_MicroMsg.db;");
  });

攔截查詢

  Sql攔截

    針對SQL攔截,這裡我會直接貼上我之前有一篇文章aop的程式碼,來作為講解,其中有用到了DBInterceptor作為攔截器攔截DBCommand進行sql攔截,實現讀寫分離的方式,下面的程式碼是我自己實現了DBCommandInterceptor來實現的一個攔截器,在DBContext中將攔截器新增進去,在每次執行查詢或者增加刪除修改的時候,都會進入這個攔截器,從而實現自己想要的業務邏輯,我在此處是寫了一個簡單的讀寫分離,感興趣的可以看看之前的文章https://www.cnblogs.com/1996-Chinese-Chen/p/15776120.html這個文章的程式碼地址已經失效,最後我會將本例程的所有程式碼放在百度網路硬碟,其中包括這個AOP的程式碼,感興趣的朋友可以下載看看。

    var list=new List<IInterceptor>();
    list.Add(new DbContextInterceptor());
    optionsBuilder.AddInterceptors(list);
    public class DbContextInterceptor:DbCommandInterceptor
    {
        private  DbConnection _connection;
        private  DbCommand _command;
        private CommandSource _commandSource;
        public DbContextInterceptor()
        {

        }
        public override DbCommand CommandCreated(CommandEndEventData eventData, DbCommand result)
        {
            return _command;
        }
        public override InterceptionResult<DbCommand> CommandCreating(CommandCorrelatedEventData eventData, InterceptionResult<DbCommand> result)
        {
            _commandSource = eventData.CommandSource;
            if (eventData.CommandSource==CommandSource.LinqQuery)
            {
                _connection = new MySqlConnection(eventData.Connection.ConnectionString);
                _command = new MySqlCommand();
            }
            return InterceptionResult<DbCommand>.SuppressWithResult(_command);
        }
        public override void CommandFailed(DbCommand command, CommandErrorEventData eventData)
        {
            base.CommandFailed(command, eventData);
        }
        public override Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken = default)
        {
            return base.CommandFailedAsync(command, eventData, cancellationToken);
        }
        public override InterceptionResult DataReaderDisposing(DbCommand command, DataReaderDisposingEventData eventData, InterceptionResult result)
        {
            return base.DataReaderDisposing(command, eventData, result);
        }
        public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
        {
            return base.NonQueryExecuted(command, eventData, result);
        }
        public override ValueTask<int> NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default)
        {
            return base.NonQueryExecutedAsync(command, eventData, result, cancellationToken);
        }
        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
        {
            return base.NonQueryExecuting(command, eventData, result);
        }
        public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
        {
            return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
        }
        public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
        {
            return base.ReaderExecuted(command, eventData, result);
        }
        public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
        {
            return base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
        }
        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
        {
            command.CommandText = "";
            return base.ReaderExecuting(command, eventData, result);
        }
        public async override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
        {
            InterceptionResult<DbDataReader> results;
            if (_commandSource == CommandSource.LinqQuery)
            {
                var connect = new MySqlConnection("data source=192.168.21.129;database=MasterSlave; userid=root;pwd=199645; charset=utf8;ConvertZeroDateTime=True;pooling=true; allowuservariables=true;");
                connect.Open();
                _command = new MySqlCommand(command.CommandText, connect);
                var reader = await _command.ExecuteReaderAsync();
                results = InterceptionResult<DbDataReader>.SuppressWithResult(reader);
            }
            else
            {
                results=await base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
            }
            return results;
        }
        public override object ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object result)
        {
            return base.ScalarExecuted(command, eventData, result);
        }
        public override ValueTask<object> ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object result, CancellationToken cancellationToken = default)
        {
            return base.ScalarExecutedAsync(command, eventData, result, cancellationToken);
        }
        public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
        {
            return base.ScalarExecuting(command, eventData, result);
        }
        public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
        {
            return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
        }
    }

  表示式攔截

    上面我們講了SQL攔截,接下來我們講一下表示式攔截,我們都知道,EF的核心在於表示式樹,可以說表示式樹構造了整個EF的核心,關於表示式樹,我在我的第一篇部落格就寫了很多關於表示式樹的案例,https://www.cnblogs.com/1996-Chinese-Chen/p/14987967.html,感興趣的朋友可以看看,所以此處表示式樹我不會做講解,只有如何實現自定義的表示式樹攔截,

    重要的有三個我們需要實現的介面,一個是IQueryable,IQueryCompiler,還有一個IAsyncQueryProvider,通過實現這三個介面,外加IDatabase,IQueryContextFactory需要用到的兩個介面,實際上只是表示式攔截,只需要實現一個IQueryCompiler也可以實現,我i自己是實現了這三個,最主要的還是在IQueryCompiler,接下來看看具體的實現程式碼。

    IQueryCompiler

public class TestQueryCompiler : IQueryCompiler
    {
        public TestQueryCompiler(IDatabase database, IQueryContextFactory queryContextFactory)
        {
            Database = database;
            var a = Database.GetType();
            QueryContextFactory = queryContextFactory;
        }

        public IDatabase Database { get; }
        public IQueryContextFactory QueryContextFactory { get; }

        public Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query)
        {
            var queryFunc = Database.CompileQuery<TResult>(query, true);
            return queryFunc;
        }

        public Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query)
        {
            var queryFunc = Database.CompileQuery<TResult>(query, false);
            return queryFunc;
        }

        public TResult Execute<TResult>(Expression query)
        {
            var queryFunc = Database.CompileQuery<TResult>(query, false);
            var res = queryFunc(QueryContextFactory.Create());
            return res;
        }

        public TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancellationToken)
        {
            var queryFunc = Database.CompileQuery<TResult>(query, true);
            var res = queryFunc(QueryContextFactory.Create());
            return res;
        }
    }

    IAsyncQueryProvider

    

 public class TestQueryProvider : IAsyncQueryProvider
 {
     public TestQueryProvider(IDBGetName ta, IQueryCompiler query,IWebGetName webGetName)
     {
         Ta = ta;
         Query = query;
         WebGetName = webGetName;
     }

     public IDBGetName Ta { get; }
     public IQueryCompiler Query { get; }
     public IWebGetName WebGetName { get; }

     public IQueryable CreateQuery(Expression expression)
     {
         return new Queryable<object>(this, expression);
     }

     public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
     {
         return new Queryable<TElement>(this, expression);
     }

     public object? Execute(Expression expression)
     {
         return Query.Execute<object>(expression);
     }

     public TResult Execute<TResult>(Expression expression)
     {
         return Query.Execute<TResult>(expression);
     }

     public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)
     {
         return Query.ExecuteAsync<TResult>(expression, cancellationToken);
     }
 }  

    IQueryable

 public class Queryable<T> : IQueryable<T>
 {
     public Queryable(IAsyncQueryProvider queryProvider, Expression expression)
     {
         QueryProvider = queryProvider;
         Expression = expression;
     }
     public Type ElementType => typeof(T);

     public IAsyncQueryProvider QueryProvider { get; }
     public Expression Expression { get; }

     public IQueryProvider Provider => QueryProvider;

     public IEnumerator GetEnumerator()
     {
         return Provider.Execute<IEnumerable>(Expression).GetEnumerator();
     }

     IEnumerator<T> IEnumerable<T>.GetEnumerator()
     {
         return Provider.Execute<IEnumerable<T>>(Expression).GetEnumerator();
     }
 }

    在實現了那三個介面之後,我們就需要將我們的服務使用DBContextOptionsBuilder的ReplaceService替換掉,這樣,在執行查詢的時候就會走我們建立的TestQueryProvider,然後我們在這個類裡呼叫關於Queryable和TestQueryCompiler來執行查詢,如果又需要修改,也可以修改Expression從而達到攔截修改。我們最終是需要藉助IDataBase的CompileQuery方法來實現構建查詢的委託,從而實現查詢,在底層還有Visitor遍歷表示式樹,當然了,此處我只展示一個攔截表示式樹,後續的原始碼講解會看到,歡迎大家關注。

    如果是使用了EF的IOC託管到了Web的IOC,只需要正常注入服務就行,生命週期是Scope,

 #未接管
optionsBuilder.ReplaceService<IAsyncQueryProvider,TestQueryProvider >(); optionsBuilder.ReplaceService<IQueryCompiler,TestQueryCompiler>();
#接管IOC

   builder.Services.AddScoped<IAsyncQueryProvider, TestQueryProvider>();
   builder.Services.AddScoped<IQueryCompiler, TestQueryCompiler>(s =>
   {
   var database = s.GetService<IDatabase>();
   var factory = s.GetService<IQueryContextFactory>();
   return new TestQueryCompiler(database, factory);
   });

 

 

 快取查詢方法

    在上面的程式碼中,我們可以看到我們呼叫了一個ComileQuery方法,構建了一個委託,實際上,我們在業務編碼中,也可以使用快取查詢,來提升業務系統的效能,雖然我們不能使用IDataBase的這個發給發,但是EF提供了一個靜態類,裡面的ComileQuery方法支援構建查詢的委託,

     看下面程式碼,我們可以呼叫這個方法快取一個查詢的方法,後面就不會再去呼叫很多的類,很多的方法來實現我們的查詢,可以快取起來,來提升我們的查詢效能,同時這個ComileQuery方法最終也會呼叫到我們上面自定義的TestQueryCompiler的ComileQuery方法去,並且同步對同步,非同步對非同步.

  public class WeatherForecastController : ControllerBase
  {
      private static readonly string[] Summaries = new[]
      {
      "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
  };
      private Func<DecodeMicroMsgContext, IQueryable<Contact>> Func;
      private readonly ILogger<WeatherForecastController> _logger;
      private readonly IWebGetName webGetName;

      public WeatherForecastController(ILogger<WeatherForecastController> logger,IWebGetName webGetName, DecodeMicroMsgContext dbContext)
      {
          _logger = logger;
          this.webGetName = webGetName;
          DbContext = dbContext;
      }

      public DecodeMicroMsgContext DbContext { get; }

      [HttpGet(Name = "GetWeatherForecast")]
      public List<Contact> Get()
      {
          Func = EF.CompileQuery<DecodeMicroMsgContext, IQueryable<Contact>>(s => s.Contacts.Take(10));
          return DbContext.Contacts.Take(10).ToList();
      }

EF的IOC託管到WEB的IOC

    在上面講原始碼的時候,我們提到了一個方法,叫UseInternalServiceProvider,我們需要藉助這個方法來把EF的ioc託管到web,同樣是DBContextOptionsBuilder的方法,將web的ServiceProvider獲取到並且呼叫這個方法,同時,我們還需要在Program.cs呼叫一個方法  builder.Services.AddEntityFrameworkSqlite();如果是其他資料庫也是一樣的道理,需要呼叫這樣的方法,可以看第三方提供的庫原始碼,或者檔案,這裡我是SQLITE資料庫,就呼叫這個方法,這個方法是將SqlLite的一些服務注入到容器裡,並且將我們的web容器傳入到EF裡面去,這樣EF注入的服務是和Web注入的服務是在一起的,然後在呼叫UseInternalServiceProvider指定ServiceProvider就可以實現EF和WEB共用一個IOC。

    下面是Program的Main方法的所有程式碼。

 public static void Main(string[] args)
 {
     var builder = WebApplication.CreateBuilder(args);
     builder.Services.AddScoped<IWebGetName,WebGetName>();
     builder.Services.AddScoped<IAsyncQueryProvider, TestQueryProvider>();
     builder.Services.AddScoped<IQueryCompiler, TestQueryCompiler>(s =>
     {
         var database = s.GetService<IDatabase>();
         var factory = s.GetService<IQueryContextFactory>();
         return new TestQueryCompiler(database, factory);
     });
     builder.Services.AddScoped<IDBGetName, DBGetName>();
     builder.Services.AddEntityFrameworkSqlite();
     builder.Services.AddDbContext<DecodeMicroMsgContext>((a, m) => {
         m.UseSqlite("Data Source=C:\\Users\\Chenxd\\Desktop\\資料\\CrackMsg\\CrackDb\\CrackDb\\bin\\Debug\\net7.0-windows\\TOOLS\\output\\decode_MicroMsg.db;");
     });
     builder.Services.AddControllers();
     // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
     builder.Services.AddEndpointsApiExplorer();
     builder.Services.AddSwaggerGen();
     builder.Services.AddSingleton(() => builder.Services);
     var app = builder.Build();

     // Configure the HTTP request pipeline.
     if (app.Environment.IsDevelopment())
     {
         app.UseSwagger();
         app.UseSwaggerUI();
     }

     app.UseAuthorization();


     app.MapControllers();

     app.Run();
 }

 

    public static IServiceCollection AddEntityFrameworkSqlite(this IServiceCollection serviceCollection)
    {
        var builder = new EntityFrameworkRelationalServicesBuilder(serviceCollection)
            .TryAdd<LoggingDefinitions, SqliteLoggingDefinitions>()
            .TryAdd<IDatabaseProvider, DatabaseProvider<SqliteOptionsExtension>>()
            .TryAdd<IRelationalTypeMappingSource, SqliteTypeMappingSource>()
            .TryAdd<ISqlGenerationHelper, SqliteSqlGenerationHelper>()
            .TryAdd<IRelationalAnnotationProvider, SqliteAnnotationProvider>()
            .TryAdd<IModelValidator, SqliteModelValidator>()
            .TryAdd<IProviderConventionSetBuilder, SqliteConventionSetBuilder>()
            .TryAdd<IModificationCommandBatchFactory, SqliteModificationCommandBatchFactory>()
            .TryAdd<IRelationalConnection>(p => p.GetRequiredService<ISqliteRelationalConnection>())
            .TryAdd<IMigrationsSqlGenerator, SqliteMigrationsSqlGenerator>()
            .TryAdd<IRelationalDatabaseCreator, SqliteDatabaseCreator>()
            .TryAdd<IHistoryRepository, SqliteHistoryRepository>()
            .TryAdd<IRelationalQueryStringFactory, SqliteQueryStringFactory>()
            .TryAdd<IMethodCallTranslatorProvider, SqliteMethodCallTranslatorProvider>()
            .TryAdd<IAggregateMethodCallTranslatorProvider, SqliteAggregateMethodCallTranslatorProvider>()
            .TryAdd<IMemberTranslatorProvider, SqliteMemberTranslatorProvider>()
            .TryAdd<IQuerySqlGeneratorFactory, SqliteQuerySqlGeneratorFactory>()
            .TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory, SqliteQueryableMethodTranslatingExpressionVisitorFactory>()
            .TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, SqliteSqlTranslatingExpressionVisitorFactory>()
            .TryAdd<IQueryTranslationPostprocessorFactory, SqliteQueryTranslationPostprocessorFactory>()
            .TryAdd<IUpdateSqlGenerator>(
                sp =>
                {
                    // Support for the RETURNING clause on INSERT/UPDATE/DELETE was added in Sqlite 3.35.
                    // Detect which version we're using, and fall back to the older INSERT/UPDATE+SELECT behavior on legacy versions.
                    var dependencies = sp.GetRequiredService<UpdateSqlGeneratorDependencies>();

                    return new Version(new SqliteConnection().ServerVersion) < new Version(3, 35)
                        ? new SqliteLegacyUpdateSqlGenerator(dependencies)
                        : new SqliteUpdateSqlGenerator(dependencies);
                })
            .TryAdd<ISqlExpressionFactory, SqliteSqlExpressionFactory>()
            .TryAddProviderSpecificServices(
                b => b.TryAddScoped<ISqliteRelationalConnection, SqliteRelationalConnection>());

        builder.TryAddCoreServices();

        return serviceCollection;
    }

 

using EfDemo.Models;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore;
using EfDemo.QueryableExtension;
using EfDemo.DBExtension;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace EfDemo.DB
{

    public partial class DecodeMicroMsgContext : DbContext
    {
        public DecodeMicroMsgContext(Func<IServiceCollection> func, IServiceProvider serviceProvider)
        {
            Func = func;
            ServiceProvider = serviceProvider;
        }

        public DecodeMicroMsgContext(DbContextOptions<DecodeMicroMsgContext> options, Func<IServiceCollection> func,IServiceProvider serviceProvider)
            : base(options)
        {

            Func = func;
            ServiceProvider = serviceProvider;
        }

        public virtual DbSet<AppInfo> AppInfos { get; set; }

        public virtual DbSet<BizInfo> BizInfos { get; set; }

        public virtual DbSet<BizName2Id> BizName2Ids { get; set; }

        public virtual DbSet<BizProfileInfo> BizProfileInfos { get; set; }

        public virtual DbSet<BizProfileV2> BizProfileV2s { get; set; }

        public virtual DbSet<BizSessionNewFeed> BizSessionNewFeeds { get; set; }

        public virtual DbSet<ChatInfo> ChatInfos { get; set; }

        public virtual DbSet<ChatLiveInfo> ChatLiveInfos { get; set; }

        public virtual DbSet<ChatRoom> ChatRooms { get; set; }

        public virtual DbSet<ChatRoomInfo> ChatRoomInfos { get; set; }

        public virtual DbSet<ChatroomTool> ChatroomTools { get; set; }

        public virtual DbSet<Contact> Contacts { get; set; }

        public virtual DbSet<ContactHeadImgUrl> ContactHeadImgUrls { get; set; }

        public virtual DbSet<ContactLabel> ContactLabels { get; set; }

        public virtual DbSet<DelayDownLoad> DelayDownLoads { get; set; }

        public virtual DbSet<FtschatroomTran> FtschatroomTrans { get; set; }

        public virtual DbSet<FtscontactTran> FtscontactTrans { get; set; }

        public virtual DbSet<MainConfig> MainConfigs { get; set; }

        public virtual DbSet<OpLog> OpLogs { get; set; }

        public virtual DbSet<PatInfo> PatInfos { get; set; }

        public virtual DbSet<RevokeMsgStorage> RevokeMsgStorages { get; set; }

        public virtual DbSet<Session> Sessions { get; set; }

        public virtual DbSet<TicketInfo> TicketInfos { get; set; }

        public virtual DbSet<TopStoryReddotInfo> TopStoryReddotInfos { get; set; }
        public IServiceProvider ServiceProvider { get; }
        public IServiceCollection ServiceDescriptors { get; }
        public Func<IServiceCollection> Func { get; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseInternalServiceProvider(ServiceProvider);
           // optionsBuilder.ReplaceService<IAsyncQueryProvider, TestQueryProvider>();
           // optionsBuilder.ReplaceService<IQueryCompiler, TestQueryCompiler>();
            // ((IDbContextOptionsBuilderInfrastructure)s).AddOrUpdateExtension(new Extension());
            //var interceptor = new List<IInterceptor>();
            //optionsBuilder.AddInterceptors();
            ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(new ServiceExtension(Func));
            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<AppInfo>(entity =>
            {
                entity.HasKey(e => e.InfoKey);

                entity.ToTable("AppInfo");

                entity.Property(e => e.Description4EnUs).HasColumnName("Description4EnUS");
                entity.Property(e => e.Description4ZhTw).HasColumnName("Description4ZhTW");
                entity.Property(e => e.Name4EnUs).HasColumnName("Name4EnUS");
                entity.Property(e => e.Name4ZhTw).HasColumnName("Name4ZhTW");
                entity.Property(e => e.Version).HasColumnType("INT");
            });

            modelBuilder.Entity<BizInfo>(entity =>
            {
                entity.HasKey(e => e.UserName);

                entity.ToTable("BizInfo");

                entity.Property(e => e.AcceptType).HasDefaultValueSql("0");
                entity.Property(e => e.BrandFlag).HasDefaultValueSql("0");
                entity.Property(e => e.BrandIconUrl).HasColumnName("BrandIconURL");
                entity.Property(e => e.Reserved1).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved3).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved5).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved7).HasDefaultValueSql("0");
                entity.Property(e => e.Type).HasDefaultValueSql("0");
                entity.Property(e => e.UpdateTime).HasDefaultValueSql("0");
            });

            modelBuilder.Entity<BizName2Id>(entity =>
            {
                entity.HasKey(e => e.UsrName);

                entity.ToTable("BizName2ID");
            });

            modelBuilder.Entity<BizProfileInfo>(entity =>
            {
                entity.HasKey(e => e.TableIndex);

                entity.ToTable("BizProfileInfo");

                entity.HasIndex(e => e.TableIndex, "versionIdx");

                entity.Property(e => e.TableIndex)
                    .ValueGeneratedNever()
                    .HasColumnName("tableIndex");
                entity.Property(e => e.TableDesc).HasColumnName("tableDesc");
                entity.Property(e => e.TableVersion)
                    .HasColumnType("INTERGER")
                    .HasColumnName("tableVersion");
            });

            modelBuilder.Entity<BizProfileV2>(entity =>
            {
                entity.HasKey(e => e.TalkerId);

                entity.ToTable("BizProfileV2");

                entity.HasIndex(e => e.TimeStamp, "BizProfileV2TimeIdx");

                entity.Property(e => e.TalkerId).ValueGeneratedNever();
            });

            modelBuilder.Entity<BizSessionNewFeed>(entity =>
            {
                entity.HasKey(e => e.TalkerId);

                entity.HasIndex(e => e.CreateTime, "BizSessionNewFeedsCreateTimeIdx");

                entity.HasIndex(e => e.UpdateTime, "BizSessionNewFeedsUpdateTimeIdx");

                entity.Property(e => e.TalkerId).ValueGeneratedNever();
            });

            modelBuilder.Entity<ChatInfo>(entity =>
            {
                entity
                    .HasNoKey()
                    .ToTable("ChatInfo");

                entity.HasIndex(e => e.Username, "ChatInfoUserNameIndex");
            });

            modelBuilder.Entity<ChatLiveInfo>(entity =>
            {
                entity
                    .HasNoKey()
                    .ToTable("ChatLiveInfo");

                entity.HasIndex(e => new { e.RoomName, e.LiveId }, "IX_ChatLiveInfo_RoomName_LiveId").IsUnique();

                entity.HasIndex(e => e.LiveId, "ChatLiveInfoLiveIdIdx");

                entity.HasIndex(e => e.RoomName, "ChatLiveInfoRoomNamex");
            });

            modelBuilder.Entity<ChatRoom>(entity =>
            {
                entity.HasKey(e => e.ChatRoomName);

                entity.ToTable("ChatRoom");

                entity.Property(e => e.ChatRoomFlag)
                    .HasDefaultValueSql("0")
                    .HasColumnType("INT");
                entity.Property(e => e.IsShowName).HasDefaultValueSql("0");
                entity.Property(e => e.Owner).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved1).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved3).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved5).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved7).HasDefaultValueSql("0");
            });

            modelBuilder.Entity<ChatRoomInfo>(entity =>
            {
                entity.HasKey(e => e.ChatRoomName);

                entity.ToTable("ChatRoomInfo");

                entity.Property(e => e.AnnouncementPublishTime).HasDefaultValueSql("0");
                entity.Property(e => e.ChatRoomStatus).HasDefaultValueSql("0");
                entity.Property(e => e.InfoVersion).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved1).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved3).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved5).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved7).HasDefaultValueSql("0");
            });

            modelBuilder.Entity<ChatroomTool>(entity =>
            {
                entity
                    .HasNoKey()
                    .ToTable("ChatroomTool");

                entity.HasIndex(e => e.ChatroomUsername, "IX_ChatroomTool_ChatroomUsername").IsUnique();
            });

            modelBuilder.Entity<Contact>(entity =>
            {
                entity.HasKey(e => e.UserName);

                entity.ToTable("Contact");

                entity.HasIndex(e => e.Reserved2, "Contact_Idx0");

                entity.Property(e => e.ChatRoomNotify).HasDefaultValueSql("0");
                entity.Property(e => e.ChatRoomType).HasColumnType("INT");
                entity.Property(e => e.DelFlag).HasDefaultValueSql("0");
                entity.Property(e => e.LabelIdlist).HasColumnName("LabelIDList");
                entity.Property(e => e.Pyinitial).HasColumnName("PYInitial");
                entity.Property(e => e.RemarkPyinitial).HasColumnName("RemarkPYInitial");
                entity.Property(e => e.Reserved1).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved2).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved5).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved8).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved9).HasDefaultValueSql("0");
                entity.Property(e => e.Type).HasDefaultValueSql("0");
                entity.Property(e => e.VerifyFlag).HasDefaultValueSql("0");
            });

            modelBuilder.Entity<ContactHeadImgUrl>(entity =>
            {
                entity.HasKey(e => e.UsrName);

                entity.ToTable("ContactHeadImgUrl");

                entity.HasIndex(e => e.Reverse0, "reverse0Index");

                entity.Property(e => e.UsrName).HasColumnName("usrName");
                entity.Property(e => e.BigHeadImgUrl).HasColumnName("bigHeadImgUrl");
                entity.Property(e => e.HeadImgMd5).HasColumnName("headImgMd5");
                entity.Property(e => e.Reverse0)
                    .HasColumnType("INT")
                    .HasColumnName("reverse0");
                entity.Property(e => e.Reverse1).HasColumnName("reverse1");
                entity.Property(e => e.SmallHeadImgUrl).HasColumnName("smallHeadImgUrl");
            });

            modelBuilder.Entity<ContactLabel>(entity =>
            {
                entity.HasKey(e => e.LabelId);

                entity.ToTable("ContactLabel");

                entity.Property(e => e.LabelId).ValueGeneratedNever();
            });

            modelBuilder.Entity<DelayDownLoad>(entity =>
            {
                entity
                    .HasNoKey()
                    .ToTable("DelayDownLoad");

                entity.HasIndex(e => e.MessageServId, "IX_DelayDownLoad_MessageServId").IsUnique();
            });

            modelBuilder.Entity<FtschatroomTran>(entity =>
            {
                entity
                    .HasNoKey()
                    .ToTable("FTSChatroomTrans");

                entity.Property(e => e.DisplayName).HasColumnName("displayName");
                entity.Property(e => e.GroupUsername).HasColumnName("groupUsername");
                entity.Property(e => e.Nickname).HasColumnName("nickname");
                entity.Property(e => e.Operation).HasColumnName("operation");
                entity.Property(e => e.Reserve1).HasColumnName("reserve1");
                entity.Property(e => e.Reserve2).HasColumnName("reserve2");
                entity.Property(e => e.Username).HasColumnName("username");
            });

            modelBuilder.Entity<FtscontactTran>(entity =>
            {
                entity
                    .HasNoKey()
                    .ToTable("FTSContactTrans");

                entity.Property(e => e.Reserve1).HasColumnName("reserve1");
                entity.Property(e => e.Reserve2).HasColumnName("reserve2");
                entity.Property(e => e.Username).HasColumnName("username");
            });

            modelBuilder.Entity<MainConfig>(entity =>
            {
                entity.HasKey(e => e.Key);

                entity.ToTable("MainConfig");

                entity.HasIndex(e => e.Reserved0, "MainConfigReserved0Idx");

                entity.HasIndex(e => e.Reserved1, "MainConfigReserved1Idx");

                entity.Property(e => e.Reserved0).HasColumnType("INT");
                entity.Property(e => e.Reserved1).HasColumnType("INT");
            });

            modelBuilder.Entity<OpLog>(entity =>
            {
                entity.ToTable("OpLog");

                entity.Property(e => e.Id)
                    .ValueGeneratedNever()
                    .HasColumnName("ID");
                entity.Property(e => e.CmditemBuffer).HasColumnName("CMDItemBuffer");
            });

            modelBuilder.Entity<PatInfo>(entity =>
            {
                entity.HasKey(e => e.Username);

                entity.ToTable("PatInfo");

                entity.Property(e => e.Username).HasColumnName("username");
                entity.Property(e => e.Reserved1)
                    .HasDefaultValueSql("0")
                    .HasColumnName("reserved1");
                entity.Property(e => e.Reserved2)
                    .HasDefaultValueSql("0")
                    .HasColumnName("reserved2");
                entity.Property(e => e.Reserved3)
                    .HasDefaultValueSql("0")
                    .HasColumnName("reserved3");
                entity.Property(e => e.Reserved4)
                    .HasDefaultValueSql("0")
                    .HasColumnName("reserved4");
                entity.Property(e => e.Reserved5).HasColumnName("reserved5");
                entity.Property(e => e.Reserved6).HasColumnName("reserved6");
                entity.Property(e => e.Reserved7).HasColumnName("reserved7");
                entity.Property(e => e.Reserved8).HasColumnName("reserved8");
                entity.Property(e => e.Reserved9).HasColumnName("reserved9");
                entity.Property(e => e.Suffix).HasColumnName("suffix");
            });

            modelBuilder.Entity<RevokeMsgStorage>(entity =>
            {
                entity.HasKey(e => e.CreateTime);

                entity.ToTable("RevokeMsgStorage");

                entity.HasIndex(e => e.MsgSvrId, "MsgSvrId_Idx");

                entity.HasIndex(e => e.RevokeSvrId, "RevokeSvrID_Idx");

                entity.Property(e => e.CreateTime).ValueGeneratedNever();
                entity.Property(e => e.MsgSvrId)
                    .HasColumnType("INTERGER")
                    .HasColumnName("MsgSvrID");
                entity.Property(e => e.RevokeSvrId)
                    .HasColumnType("INTERGER")
                    .HasColumnName("RevokeSvrID");
            });

            modelBuilder.Entity<Session>(entity =>
            {
                entity.HasKey(e => e.StrUsrName);

                entity.ToTable("Session");

                entity.HasIndex(e => e.NOrder, "nOrderIndex");

                entity.Property(e => e.StrUsrName).HasColumnName("strUsrName");
                entity.Property(e => e.BytesXml).HasColumnName("bytesXml");
                entity.Property(e => e.EditContent).HasColumnName("editContent");
                entity.Property(e => e.NIsSend).HasColumnName("nIsSend");
                entity.Property(e => e.NMsgLocalId).HasColumnName("nMsgLocalID");
                entity.Property(e => e.NMsgStatus).HasColumnName("nMsgStatus");
                entity.Property(e => e.NMsgType).HasColumnName("nMsgType");
                entity.Property(e => e.NOrder)
                    .HasDefaultValueSql("0")
                    .HasColumnType("INT")
                    .HasColumnName("nOrder");
                entity.Property(e => e.NStatus).HasColumnName("nStatus");
                entity.Property(e => e.NTime).HasColumnName("nTime");
                entity.Property(e => e.NUnReadCount)
                    .HasDefaultValueSql("0")
                    .HasColumnName("nUnReadCount");
                entity.Property(e => e.OthersAtMe)
                    .HasColumnType("INT")
                    .HasColumnName("othersAtMe");
                entity.Property(e => e.ParentRef).HasColumnName("parentRef");
                entity.Property(e => e.Reserved0).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved2).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved4).HasDefaultValueSql("0");
                entity.Property(e => e.StrContent).HasColumnName("strContent");
                entity.Property(e => e.StrNickName).HasColumnName("strNickName");
            });

            modelBuilder.Entity<TicketInfo>(entity =>
            {
                entity.HasKey(e => e.UserName);

                entity.ToTable("TicketInfo");

                entity.Property(e => e.Reserved1).HasDefaultValueSql("0");
                entity.Property(e => e.Reserved3).HasDefaultValueSql("0");
            });

            modelBuilder.Entity<TopStoryReddotInfo>(entity =>
            {
                entity.HasKey(e => e.MsgId);

                entity.ToTable("TopStoryReddotInfo");

                entity.Property(e => e.H5version).HasColumnName("H5Version");
            });

            OnModelCreatingPartial(modelBuilder);
        }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    }
}

結尾

    在本文中,我們一共講了AddDbContext做了什麼,DBContext的建構函式又做了那些事情,在寫了不託管EF的ioc到WEB的ioc的場景下如果注入服務到EF的ioc中,以及如何攔截增刪改查的方式,提升查詢效能的方式,以及最後的EF的ioc託管到WEB的ioc,本文作為原始碼講解的第一章,覺得寫的有點多,如果又看不懂的地方,或者程式碼下載下來沒辦法執行或者儲存的地方可以隨時聯絡我,QQ934550201.我們下次再見。資料庫是我破解的我原生的微信資料的一部分,emmm作為了本次的例子,我希望大家能夠合理看待我的這個資料庫的資料,不要做一些不好的事情,謝謝大家。

    本次的程式碼例子地址

               連結:https://pan.baidu.com/s/1w6kFG5MCJYE0jzEBxjQTKw

               提取碼:foin

    之前AOP的程式碼例子

    連結:https://pan.baidu.com/s/1AJe4-KhjIESbDtFNqM968Q
    提取碼:jp20