【EF Core】通过 DbContext 选项扩展框架

news/2025/10/6 12:08:03/文章来源:https://www.cnblogs.com/tcjiaan/p/19127370

本来老周计划在 10 月 1 日或 2 日写这篇水文的,没打算出去玩(确实没啥好玩)。不过因为买的运动相机到手,急着想试试效果,于是就备了些干粮,骑着山地车在外面鬼混了一天。10 月 2 日,家里来了三位热爱学习的小妹妹,必须传道授业解惑。10 月 3 日去表弟家里挑一只战斗力强的狸花猫,负责家里的治安。4、5 日清洗电风扇和一台有霉味的圆柱空调,顺便把家里的门窗都清洗一下。只好等到中秋节才来写文章。

EF Core 内部使用了 IoC 容器,使其支持依赖注入,理论上也很容易扩展。不过,框架有缓存自己的服务列表,咱们无法直接访问服务容器。目前阶段,EF Core 还不能传递咱们自己的 App Services——初始化时它会直接改为 null。

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

所以,就算你有本事往 Options 里面塞 App Services 也不起作用,人家直接给干成 null 了。微软社区团队表示将来会支持的。

先不要灰心,并不是不能扩展的,还有一个扩展点可以利用—— DbContext 的选项类。

其实,DbContext 选项类是由一组 IDbContextOptionsExtension 服务构成的。所以,咱们如果实现这个服务接口,然后放进选项类的扩展列表中,也能实现扩展 EF Core 的功能。先来认识一下,IDbContextOptionsExtension 接口规定了哪些成员。

1、Info 属性:返回类型为 DbContextOptionsExtensionInfo。注意各位,这是个抽象类,所以你必须实现自己的 Info,主要用于返回你正在编写的扩展的相关信息。这个类咱们后面再讨论。

2、ApplyDefaults 方法:这个方法有个默认实现,就是 return this。其作用是根据参数传入的 DbContextOptions(另一个选项类实例),给当前扩展设置一些默认值。这个一般用于:需要根据选项来设置某些参数值,比如,SqlServerOptionsExtension 类。

 public virtual IDbContextOptionsExtension ApplyDefaults(IDbContextOptions options){if (ExecutionStrategyFactory == null&& (EngineType == SqlServerEngineType.AzureSql|| EngineType == SqlServerEngineType.AzureSynapse|| UseRetryingStrategyByDefault)){return WithExecutionStrategyFactory(c => new SqlServerRetryingExecutionStrategy(c));}return this;}

3、ApplyServices 方法:重点来了。对就是它,实现它,你就能向服务容器添加自定义的服务了。

4、Validate 方法:验证一下当前 DbContextOptions 的值是否符合你的要求,如果验证不通过,直接抛异常就行了。如果不需要验证,留空即可。

 

现在咱们再来认识一下 DbContextOptionsExtensionInfo 抽象类。

1、LogFragment  属性:返回一个字符串,在记录日志时,这个字符串会出现在日志里。至于说是什么字符串,你可自己决定。

2、Extension 属性:返回与当前信息类相关的 IDbContextOptionsExtension 对象。

3、ShouldUseSameServiceProvider 方法:这个方法其实是在 DbContextOptions 类的 Equals 方法中作为判断两个 DbContextOptions 实例的配置是否相同的条件之一。意思就是如果结果是 true,表明所有配置相同的 DbContextOptions 不需要初始化新的服务容器。

4、GetServiceProviderHashCode 与 PopulateDebugInfo 方法:.NET CLR 对象的相等判断除了 Equals 方法,还有 GetHashCode 方法,看看是否返回相同的哈希。GetServiceProviderHashCode 方法你可以自定义返回的哈希值,DbContextOptions 类的 GetHashCode 方法中也调用了此方法。

public override int GetHashCode()
{var hashCode = new HashCode();foreach (var (type, value) in _extensionsMap){hashCode.Add(type);hashCode.Add(value.Extension.Info.GetServiceProviderHashCode());}return hashCode.ToHashCode();
}

可见,如果 GetServiceProviderHashCode 方法返回的值改变,就会影响到 DbContextOptions 对象的相等判断。同样,GetServiceProviderHashCode 方法会和 PopulateDebugInfo 方法搭配用。PopulateDebugInfo 方法的参数是一个字典,开发者可以往里面设置一些自定义的 Key-Value 元素,这些元素通常表示当前扩展中被更改的值。

PopulateDebugInfo 方法中向字典添加的值也会被传递给 ServiceProviderDebugInfo 事件,事件相关的数据被封装到 ServiceProviderDebugInfoEventData  类中。

不过,但是,当你开启日志功能后,你会发现,ServiceProviderDebugInfo 事件根本不会输出到日志中。Github 上有人提过这事,但没人回答。在本文后面,老周会告诉你如何解决此问题。

----------------------------------------------------------------------------------------------------------------------------------------------------

上面是对知识点的简单理论介绍。说简单一点,就是你想扩展 EF Core,就是实现 IDbContextOptionsExtension 接口,然后在 ApplyServices 方法中把你的服务放进容器

理论总是抽象的,咱们动手练一练就好了。

第一步,老规矩,随便写个实体,然后从 DbContext 继承一个你的上下文。

public class Pet
{public int Id { get; set; }public string Name { get; set; } = "未知生物";public int? Age { get; set; }
}public class DemoDbContext : DbContext
{// 公共属性:数据集public DbSet<Pet> Pets { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder opBuilder){opBuilder.UseSqlite("data source=:memory:")// 开启日志.LogTo(log => Console.WriteLine(log));}
}

第二步,既然咱们要扩展,当然要写服务类型了。

public interface IHelloWorld
{void SayHello(string? who);
}public class DemoHelloWorld : IHelloWorld
{public void SayHello(string? who){Console.WriteLine("你好,{0}", who ?? "宇宙人");}
}

当然了,通过构造函数的依赖注入,你是可以访问 EF Core 内部的服务的。这里为了演示的简单,就没有注入任何东西。

第三步,重点来了,实现那个,那个很辣眼睛的接口。

public class DemoDbContextOptionsExtension : IDbContextOptionsExtension
{// 扩展信息private MyExtInfo? _info;// 这个属性用于返回扩展信息。// 返回的类型是 DbContextOptionsExtensionInfo,不需要访问 MyExtInfo 类public DbContextOptionsExtensionInfo Info => _info ??= new MyExtInfo(this);public void ApplyServices(IServiceCollection services){// 这里,添加你的服务services.AddScoped<IHelloWorld, DemoHelloWorld>();}public void Validate(IDbContextOptions options){// 不需要验证,空方法
    }// 一个随机整数,模拟选项改变public int RandValue { get; set; }// 这个类私有化就可以了,因为对外公开的是 DbContextOptionsExtensionInfo 类型private class MyExtInfo : DbContextOptionsExtensionInfo{// 构造函数public MyExtInfo(IDbContextOptionsExtension extension) : base(extension){}// 替换一下基类成员,方便获取new public DemoDbContextOptionsExtension Extension => (DemoDbContextOptionsExtension)base.Extension;// 此处要返回 false,因为咱们这个不是数据库提供者public override bool IsDatabaseProvider => false;// 自定义日志输出public override string LogFragment => $"这是个大扩展 - { Extension.RandValue.ToString()}";// 使用 _myRandValue 的哈希,public override int GetServiceProviderHashCode(){return Extension.RandValue.GetHashCode();}public override void PopulateDebugInfo(IDictionary<string, string> debugInfo){// 设置调试信息debugInfo["MyExtension:RandomValue"] = Extension.RandValue.ToString();}public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other){// 可以直接 retrun truereturn other is MyExtInfo;}}
}

第四步,将自定义扩展添加到 DbContextOptions 的扩展集合中有点麻烦,所以一般要封装一个扩展方法,方便调用。

public static class DemoDbContextOptionsBuilderExtensions
{public static DbContextOptionsBuilder UseDemoExt(this DbContextOptionsBuilder builder){// 在添加前应当查找一下,避免重复添加var myext = builder.Options.FindExtension<DemoDbContextOptionsExtension>();// 如果没有,就new一个myext ??= new DemoDbContextOptionsExtension();// 设置一下随机数据,模拟配置改变myext.RandValue = Random.Shared.Next(100, 9999999);// 添加到扩展集合中(注意类型转换)((IDbContextOptionsBuilderInfrastructure)builder).AddOrUpdateExtension(myext);return builder;}
}

a、要养成先找后加的习惯,即先 Find 一下扩展是不是已在集合中,然后再调用 AddOrUpdateExtension 方法添加到扩展集合中。

b、由于此方法是显式实现了 IDbContextOptionsBuilderInfrastructure 接口,所以要先把 builder 转换为 IDbContextOptionsBuilderInfrastructure 接口类型再调用 AddOrUpdateExtension 方法。

第五步,回过头去修改 DemoDbContext 类。

public class DemoDbContext : DbContext
{……protected override void OnConfiguring(DbContextOptionsBuilder opBuilder){opBuilder.UseSqlite("data source=:memory:")// 开启日志.LogTo(log => Console.WriteLine(log))// 使用自定义的扩展.UseDemoExt();}// 测试服务public void Greeting(string who)
    {
        IHelloWorld sv = this.GetService<IHelloWorld>();
        sv.SayHello(who);
    }
}

第六步,实例化上下文对象,运行,实验一下。

static void Main(string[] args)
{using var ctx = new DemoDbContext();ctx.Greeting("小王");
}

 

运行结果如下图所示。

image

 很显然,ServiceProviderDebugInfo 事件没有日志输出的。

 

现在,老周就说一下如何让它输出这两个事件。

方法:使用 .NET Logging API。比如,咱们要使日志输出到控制台,需要添加 Microsoft.Extensions.Logging.Console 包的引用。

在上下文类中,定义 ILoggerFactory 类型的字段,并用 LoggerFactory.Create 方法创建实例。

public class DemoDbContext : DbContext
{// 静态成员static  ILoggerFactory logFac = LoggerFactory.Create(lb => {
        lb.AddConsole();
        lb.SetMinimumLevel(LogLevel.Trace);
     });……protected override void OnConfiguring(DbContextOptionsBuilder opBuilder){opBuilder.UseSqlite("data source=:memory:")// 开启日志//.LogTo(log => Console.WriteLine(log)).UseLoggerFactory(logFac)
                        // 使用自定义的扩展
                        .UseDemoExt();}……
}

 SetMinimumLevel 方法将日志级别设置为 Debug 或 Trace。

在 OnConfiguring 方法中,使用 UseLoggerFactory 方法应用 LoggerFactory 对象。

修改之后,重新运行程序。结果如下。

image

 

好了,今天就水到这里吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/929291.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从Chrome渲染器代码执行到内核:MSG_OOB漏洞分析与利用

本文详细分析了Linux内核中MSG_OOB特性的安全漏洞(CVE-2025-38236),探讨了如何从Chrome渲染器沙箱中利用该漏洞实现从用户态代码执行到内核权限提升的完整攻击链,包括漏洞原理、利用技术和沙箱逃逸方法。从Chrome渲染…

assistant-ui

assistant-ui. https://langgraph.com.cn/cloud/how-tos/use_stream_react/index.htmluseStream() React hook 提供了一种将 LangGraph 无缝集成到 React 应用程序中的方式。它处理了流式传输、状态管理和分支逻辑的所…

婚纱网站怎么做临淄区最新招聘信息

grpc 与 protobuf grpc 使用的是 protobuf 协议&#xff0c;其是一个通用的 rpc 框架&#xff0c;基本支持主流的所有语言、其底层使用 http/2 进行网络通信&#xff0c;具有较高的效率 protobuf 是一种序列化格式&#xff0c;这种格式具有 序列化以及解码速度快&#xff08;…

US$34.2 KEYDIY KD B10-4 Universal Flip Remote Key 3+1 Buttons for Honda Type 5pcs/lot

KEYDIY KD B10-4 Universal Flip Remote Key 3+1 Buttons for Honda Type 5pcs/lotProduct Specifications:Manufacturer: KEYDIY Condition: New Color: Black Buttons: 4 Panic: Yes Transponder: No Proximity / Sm…

福州全网网站建设揭阳企业网页制作公司

手里有块netduino的板子&#xff0c;一直闲置未用&#xff0c;netduino具体是什么不知道的就百度吧&#xff0c;我这也不是主要讲netduino开发的&#xff0c;简单说就是用.net开发硬件&#xff0c;了解到netduino也是原来学过C#&#xff0c;当然我主要的工作还是嵌入式硬件开发…

做网站一般要了解哪些广州手机网站建设公司哪家好

在调试RK3288 Android 8.1系统遇到一个问题&#xff1a;开机启动uboot logo过渡到kernel log的过程中会花掉直到没有显示&#xff0c;再出现kernel logo。分析&#xff1a;打印串口log时发现&#xff0c;uboot阶段显示一切正常&#xff0c;进入kernel以后就开始花掉了然后变成没…

投标网站怎么做新泰做网站

原文地址&#xff1a;http://blog.csdn.net/zuochanxiaoheshang/article/details/8769198 点击阅读原文 --------------------------------------------------- Hadoop 控制输出文件命名 在一般情况下&#xff0c;Hadoop 每一个 Reducer 产生一个输出文件&#xff0c;文件以 …

《无垠的太空(2)卡利班之战》电子书素材征集

《无垠的太空(2)卡利班之战》电子书素材征集《无垠的太空(2)卡利班之战》电子书素材征集 “太空无垠”(又叫“苍穹浩瀚”)系列的第二部《卡利班之战》中文版纸质书已经出了,我想制作成电子书,哪位有pdf可以发下。或…

20251006 之所思 - 人生如梦

20251006 之所思10月6日早上9点起来,一直计划刷牙洗脸后去学英语,但是沉迷于短视频,浪费两个小时,越刷越浮躁,越刷越焦虑。浮躁是因为即时满足之后的空虚感,焦虑是因为自己计划了很多事情,但是因为一直刷手机没…

C# Avalonia 16- Animation- RotateButton

C# Avalonia 16- Animation- RotateButtonRotateButton.axaml代码<Window xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http…

US$78.85 KEYDIY KD ZB42-4 Universal Smart Remote Key 3+1 Buttons for Lexus Type 5pcs/lot

KEYDIY KD ZB42-4 Universal Smart Remote Key 3+1 Buttons for Lexus Type 5pcs/lotProduct Specifications:Manufacturer: KEYDIY Condition: New Color: Black Buttons: 4 Panic: Yes Transponder - ID: Without Tr…

2025 十一集训

/Day 1:\(2^{cn}(c<1)\) 专题选讲

详细介绍:python第31天打卡

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

US$78.85 KEYDIY KD ZB33-4 Universal Smart Remote Key 3+1 Buttons for Hyundai Type 5pcs/lot

KEYDIY KD ZB33-4 Universal Smart Remote Key 3+1 Buttons for Hyundai Type 5pcs/lotProduct Specifications:Manufacturer: KEYDIY Condition: New Color: Brown Buttons: 4 Panic: Yes Transponder - ID: Without …

Python 的 LEGB 作用域

Python 的 LEGB 作用域在 Python 的 LEGB 作用域规则中,整数(或其他变量)所处的位置取决于它的定义位置,LEGB 代表四种作用域类型,优先级从高到低为:L(Local,局部作用域) 函数内部定义的变量(包括函数参数)…

在Windows下使用lucky更新动态域名

在Windows下使用lucky更新动态域名2024.06.032.15版本之后DDNS配置进行了较大的更新 配置不再向后兼容 若进行降级 DDNS配置会丢失 配置方法详见新教程:链接关于本教程 lucky内置的DDNS功能 支持多家DDNS提供商 也允许…

20251005 模拟测 总结

\(\mathcal{Preface}\) 分数分布:\(100+100+100+100= 400\)。 AK 了,开心呀! \(\mathcal{Problem \space{} A}\) Tag:诈骗,排序,贪心。 赛时看到题,一下子没反应过来,以为是超难 DP 题。 过了 B & C 之后回…

chatjs.langchain

chatjs.langchain https://chatjs.langchain.com/ https://github.com/langchain-ai/chat-langchain Chat LangChain This repo is an implementation of a chatbot specifically focused on question answering over …

完整教程:Microsoft Word使用技巧分享(本科毕业论文版)

完整教程:Microsoft Word使用技巧分享(本科毕业论文版)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consola…

旅游网站建设的参考文献稻壳ppt模板免费下载

文章目录 vite的proxy开发环境设置如果后端没有提供可以替换的/mis等可替换的后缀的处理办法接口如何区分.env.development开发和.env.production生产环境接口在生产环境下&#xff0c;还能使用proxy代理地址吗&#xff1f; vite的proxy开发环境设置 环境&#xff1a; vite 4…