在使用外部模型那篇水文中,有大伙伴提出:老周,你那个 Ultraman 类和 Speciality 类的的关系是不是有问题,外键不应该在 Speciality 类上吗,怎么会跑到 Ultraman 类上?因为它们是一对一关系,在配置的时候你也可以反过来,主要区别是谁引用谁的问题,由于是一对一引用,所以反过来也可以的。
今天咱们聊聊实体类构造函数的依赖注入。实体类也支持依赖注入,不过,目前版本只支持注入 EF Core 自己的服务类型,你在代码中添加的应用程序级服务类型不能注入(以后可能会支持)。总结一下,以下服务类型可以注入:
1、EF Core 框架内部注册的服务类型;
2、三个补充类型:
a、当前 DbContext 实例(你从 DbContext 派生的类);
b、当前实体相关的 IEntityType,可以获取实体模型相关信息;
c、ILazyLoader 接口,延时加载时用得上。
现在要解决一个问题:我怎么知道 EF Core 框架内部哪些服务可以注入?这个活儿有两个方案:第一个方案是看 EF Core 源代码,在 EFCore\Infrastructure 下,注意看 EntityFrameworkServicesBuilder 类的 CoreServices 字段或 TryAddCoreServices 方法,基本齐全了。- public static readonly IDictionary<Type, ServiceCharacteristics> CoreServices
- = new Dictionary<Type, ServiceCharacteristics>
- {
- { typeof(<strong>LoggingDefinitions</strong>), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(<strong>IDatabaseProvider</strong>), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
- { typeof(<strong>IDbSetFinder</strong>), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(<strong>IDbSetInitializer</strong>), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IDbSetSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IEntityFinderSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IStructuralTypeMaterializerSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(ITypeMappingSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IModelCustomizer), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IModelCacheKeyFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IModelSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IModelRuntimeInitializer), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IInternalEntrySubscriber), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IEntityEntryGraphIterator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IValueGeneratorCache), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(ISingletonOptionsInitializer), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(ILoggingOptions), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(ICoreSingletonOptions), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IModelValidator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(ICompiledQueryCache), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IValueConverterSelector), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IConstructorBindingFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IRegisteredServices), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IPropertyParameterBindingFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IParameterBindingFactories), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IMemberClassifier), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IMemoryCache), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IEvaluatableExpressionFilter), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(INavigationExpansionExtensibilityHelper), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(ILiftableConstantFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IExceptionDetector), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IJsonValueReaderWriterSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
- { typeof(IProviderConventionSetBuilder), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IConventionSetBuilder), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDiagnosticsLogger<>), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IInterceptors), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(ILoggerFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IEntityGraphAttacher), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IKeyPropagator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(INavigationFixer), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(ILocalViewListener), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IStateManager), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IConcurrencyDetector), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IInternalEntityEntryNotifier), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IValueGenerationManager), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IChangeTrackerFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IChangeDetector), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDbContextServices), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IValueGeneratorSelector), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IExecutionStrategyFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IExecutionStrategy), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IAsyncQueryProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IQueryCompiler), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(ICompiledQueryCacheKeyGenerator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IModel), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDesignTimeModel), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IUpdateAdapterFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(ICurrentDbContext), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDbContextDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDatabaseFacadeDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDbContextOptions), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDatabase), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDatabaseCreator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDbContextTransactionManager), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IQueryContextFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IQueryCompilationContextFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IQueryableMethodTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IQueryTranslationPreprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IQueryTranslationPostprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IShapedQueryCompilingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IDbContextLogger), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IAdHocMapper), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(ILiftableConstantProcessor), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(ILazyLoader), new ServiceCharacteristics(ServiceLifetime.Transient) },
- { typeof(ILazyLoaderFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
- { typeof(IParameterBindingFactory), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
- { typeof(ITypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
- {
- typeof(IEvaluatableExpressionFilterPlugin),
- new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true)
- },
- { typeof(ISingletonOptions), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
- { typeof(IConventionSetPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
- { typeof(ISingletonInterceptor), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
- { typeof(IResettableService), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
- { typeof(IInterceptor), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
- { typeof(IInterceptorAggregator), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }
- };
复制代码 这个字典的 Key 就是核心服务的类型。Relational 库和数据库提供者可能会补充一些服务,但大都多是替换服务,即接口类型是不变的。
第二个方案是通过一个叫 IRegisteredServices 的服务,它可以列出已注册服务的 Type 列表。- using (var c = new DemoContext())
- {
- <strong>IRegisteredServices regsvcs </strong><strong>= c.GetService<IRegisteredServices>
- ();
- </strong> foreach (var sv in regsvcs.Services)
- {
- Console.WriteLine(sv);
- }
- ……
- }
复制代码 这样就会列出内部已注册服务的类型。严重注意:实体构造函数不能注入 IRegisteredServices 类型的服务,因为 EF Core 内部实现时,本身就是从 IRegisteredServices.Services 集合中查找可以注入的服务的,而 IRegisteredServices.Services 是不包含它自己的。说人话就是:用 IRegisteredServices 类注入是找不到该服务的。
然后解决第二个问题:在实体类的构造函数中注入服务有啥用?大多数时候没啥用,但当你要使用某些服务来获取特殊信息,或执行特殊操作(如在实体类中执行 SQL 语句)时就用得上。
咱们举个例子,就用官方文档最喜欢的 Blog 和 Post 实体来演示。其中,Blog 类的构造函数将注入 IEntityType 服务。然后可以利用这个服务来获取 Blog 实体有几个属性,有几个键,有几个导航属性。- public class Blog
- {
- // 私有字段
- private <strong>IEntityType? _thisEntity;
- </strong> // 私有构造器
- private Blog(IEntityType t) => _thisEntity = t;
- // 公共构造器
- public Blog()
- {
- }
- public int BlogID { get; set; }
- public string DisplayName { get; set; } = string.Empty;
- public string Url { get; set; } = string.Empty;
- public List<Post> PostList { get; set; } = new();
- // 以下属性通过注入的 IEntityType 获取相关值
- public int PropertyCount => _thisEntity?.GetProperties().Count() ?? 0;
- public int NavigationCount => _thisEntity?.GetNavigations().Count() ?? 0;
- public int KeyCount => _thisEntity?.GetKeys().Count() ?? 0;
- }
- public class Post
- {
- public int PostID { get; set; }
- public string Header { get; set; } = string.Empty;
- public Blog? TheBlog { get; set; }
- }
复制代码 注意,你自己 new 一个 Blog 实例是没有注入功能的,所以要定义一个无参数的公共构造函数,以供外部初始化用。另一个私有构造函数有一个 IEntityType 类型的参数,可以接收注入。因为这构造函数只由 EF Core 内部调用,代码中调用无法注入,故声明为私有即可。EF Core 在查找构造函数时,不管你是公有的还是私有的,只要不是静态的,一律能发现。
在访问 Blog 实体时就可以获取键、属性、导航属性的数量。- var q = c.Blogs.Include(b => b.PostList).ToArray();
- var first = q.FirstOrDefault();
- if (first != null)
- {
- Console.WriteLine($"实体{first.GetType().Name}有{first.
- PropertyCount}个属性,{first.KeyCount}个键,{first.
- NavigationCount}个导航属性");
- }
复制代码 得到的输出如下:
除了构造函数,也可以从属性注入。例如- [PrimaryKey(nameof(Screen.ScrID))]
- public class Screen
- {
- [Column("scr_id")]
- public int ScrID { get; set; }
- [Column("width")]
- public int Width { get; set; }
- [Column("height")]
- public int Height { get; set; }
- //----------- 注意以下属性 --------------
- public <strong>MyDBContext? ThisContext</strong> { get; set; }
- }
- public class MyDBContext : DbContext
- {
- public MyDBContext(DbContextOptions<MyDBContext> options)
- : base(options)
- { }
-
- public DbSet<Screen> Screens { get; set; }
- }
复制代码 上面示例中,MyDBContext 实例会注入到每个 Screen 实例的 ThisContext 属性。即每个实体实例的 ThisContext 属性都引用同一个 MyDBContext 对象。- var q = dc.Screens.FirstOrDefault();
- if (q != null)
- {
- if (q.ThisContext != null)
- {
- Console.WriteLine("ThisContext属性:{0}", q.ThisContext);
- }
- }
- // 输出:
- // ThisContext属性:MyDBContext
复制代码 好了,今天就水到这里了。因为这个主题确实太简单了,老周也不必举太多例子。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |