- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
在网上很少看到有关于系统讲解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 。
最后此篇关于【源码解读(一)】EFCORE源码解读之创建DBContext查询拦截的文章就讲到这里了,如果你想了解更多关于【源码解读(一)】EFCORE源码解读之创建DBContext查询拦截的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
自动生成字段值,咱们首先想到的是主键列(带 IDENTITY 的主键)。EF Core 默认的主键配置也是启用 Identity 自增长的,而且可以自动标识主键。前提是代表主键的实体属性名要符合以下规
上次老周扯了有关主、从实体的话题,本篇咱们再挖一下,主、从实体之间建立的关系,跟咱们常用的一对1、一对多这些关系之间有什么不同。 先看看咱们从学习数据库开始就特熟悉的常用关系——多对多、一对
前言 基于EF Core + MySQL的基本增删改查,示例是基于 .NET6 + EF Core + MySQL 创建实体和数据库、EFCore 数据迁移 项目基础上的内容增加。同时也是
我试图在我的上下文类中设置一对一的外键关系,但我不断收到以下错误。我找到了一个 similar post但这似乎并没有解决我的问题,因为我在模型中明确指定了关系。我确认: Server 对象引用了一个
运行 dotnet ef migrations add XYZ将导致 a Migrations directory being created在项目中。这个目录是否应该提交给版本控制(Git 等)?
我试图围绕 EF Cores 拥有的对象以及如何控制何时加载某些数据块。 基本上我有一堆旧的遗留表(一些有大约 150 列),并希望使用一个根实体和每个表的几个拥有的对象对它们进行建模,以实现更好的分
我的模型包含 Post 和 PostHistory 类,其中 Post 与 PostHistory 具有一对多关系。 class Post { public int Id { get; set
我有一个 .Net Core WebApplication 项目,其中上下文类位于类库中。如果我在 OnConfiguring(DbContextOptionsBuilder optionsBuild
我正在尝试使用 Entity Framework Core 2.0.1 将现有的 PostgreSQL 数据库转换为实体,但在搭建脚手架时出现错误。数据库是通过运行以下脚本创建的: Script To
我在一个项目中有两个实体:SupplierFinishingItem 和 ProductOptionListItem。 ProductOptionListItem 通过导航属性引用另一个。 当我尝试创
我已经在 this 之后使用 .Net core 和 EFCore 创建了一个 API使用 VSCode 的教程。 我的 MySQL 数据库有很多模型,因为我正在“迁移”我的 EF6 和 asp.ne
目录 一 代码分析 1 GetTableName 2&n
我正在尝试根据实体特定字段中的搜索值列表来过滤实体。 例如: var searchValues = new List { "abc", "xyz" }; var posts = Context.Pos
我有一个在表中创建列的迁移。 public partial class AddName : Migration { protected override void Up(MigrationBu
Ef Core 接收错误 System.InvalidOperationException: Can't process set operations afterclient evaluation,
我有以下型号。在使用 find 方法从数据库中获取时,用子实体加载父实体的更好方法是什么? 父实体: public class Client { public int Id { get; se
我正在使用 EFCore 做一个原型(prototype),并努力寻找一种返回我刚刚添加到实体集合中的对象的好方法。 例如: public Songleader AddSongleader(int c
这个问题已经有答案了: How is Identity.EntityFramework OnModelCreating called (2 个回答) 已关闭12 个月前。 我知道当您创建迁移时它会被调
这个问题已经有答案了: How is Identity.EntityFramework OnModelCreating called (2 个回答) 已关闭12 个月前。 我知道当您创建迁移时它会被调
我想从我的 ASP.net Identity Users 表中查询所有用户并将它们映射到一个简单的 DTO,如下所示: public class UserDto { public string
我是一名优秀的程序员,十分优秀!