在網上很少看到有關於系統講解EFCore原始碼的,可能大概也許是因為EFCore的原始碼總體是沒有asp.net web的原始碼流程清晰,正如群友所說,EFCore的原始碼大致看起來有點凌亂,與其說凌亂,不如說是沒有一個好的方向;然後昨天在群裡有一個朋友再說,EfCore的攔截器如何注入Web的服務,以及EfCore如何自定義查詢,我就看了一下EfCore的原始碼,在此之前我針對asp.net web 做了一個原始碼解讀,有興趣的朋友可以看前面的文章,也給別人說過啥時候講解一下efcore的原始碼,剛好藉助這麼一個機會,講一講EfCore的原始碼,本篇文章作為一個開端,會呈現一下幾點
一:首先是AddDbContext裡面做了什麼。
二:DbContext的建構函式裡面做了那些事情。
三:如何在EfCore的服務中獲取到Web注入的服務的方式之一。
四:攔截查詢的幾種方式。
五:使用快取查詢方法提升效能。
六:如何託管EFCORE的IOC容器(和Web的IOC使用同一個)
以上作為本篇文章的所有內容,接下來,我們來開始講解原始碼,動手實踐。
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建構函式的原始碼講解,這裡設計內容稍微多一些。
建構函式的程式碼是整體是沒多少的,但是最重要的還是在於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的方式)。
先上程式碼,程式碼沒有多少,就是實現這個介面,定義一個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攔截,這裡我會直接貼上我之前有一篇文章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,接下來看看具體的實現程式碼。
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; } }
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); } }
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(); }
在上面講原始碼的時候,我們提到了一個方法,叫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