【EF Core】通过 DbContext 选项扩展框架
<p>本来老周计划在 10 月 1 日或 2 日写这篇水文的,没打算出去玩(确实没啥好玩)。不过因为买的运动相机到手,急着想试试效果,于是就备了些干粮,骑着山地车在外面鬼混了一天。10 月 2 日,家里来了三位热爱学习的小妹妹,必须传道授业解惑。10 月 3 日去表弟家里挑一只战斗力强的狸花猫,负责家里的治安。4、5 日清洗电风扇和一台有霉味的圆柱空调,顺便把家里的门窗都清洗一下。只好等到中秋节才来写文章。</p><p>EF Core 内部使用了 IoC 容器,使其支持依赖注入,理论上也很容易扩展。不过,框架有缓存自己的服务列表,咱们无法直接访问服务容器。目前阶段,EF Core 还不能传递咱们自己的 App Services——初始化时它会直接改为 null。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> cacheKey =<span style="color: rgba(0, 0, 0, 1)"> options;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> extension = options.FindExtension<CoreOptionsExtension><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="background-color: rgba(153, 204, 255, 1)">extension?.ApplicationServiceProvider != <span style="color: rgba(0, 0, 255, 1)">null</span></span><span style="color: rgba(0, 0, 0, 1)">)
{
<strong><span style="background-color: rgba(255, 255, 0, 1)">cacheKey </span></strong></span><strong><span style="background-color: rgba(255, 255, 0, 1)">= ((DbContextOptions)options).WithExtension(extension.WithApplicationServiceProvider(<span style="color: rgba(0, 0, 255, 1)">null</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">))</span></strong>;
}</span></pre>
</div>
<p>所以,就算你有本事往 Options 里面塞 App Services 也不起作用,人家直接给干成 null 了。微软社区团队表示将来会支持的。</p>
<p>先不要灰心,并不是不能扩展的,还有一个扩展点可以利用—— DbContext 的选项类。</p>
<p>其实,DbContext 选项类是由一组 IDbContextOptionsExtension 服务构成的。所以,咱们如果实现这个服务接口,然后放进选项类的扩展列表中,也能实现扩展 EF Core 的功能。先来认识一下,IDbContextOptionsExtension 接口规定了哪些成员。</p>
<p>1、Info 属性:返回类型为 DbContextOptionsExtensionInfo。注意各位,这是个抽象类,所以你必须实现自己的 Info,主要用于返回你正在编写的扩展的相关信息。这个类咱们后面再讨论。</p>
<p>2、ApplyDefaults 方法:这个方法有个默认实现,就是 return this。其作用是根据参数传入的 DbContextOptions(另一个选项类实例),给当前扩展设置一些默认值。这个一般用于:需要根据选项来设置某些参数值,比如,SqlServerOptionsExtension 类。</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span><span style="color: rgba(0, 0, 0, 1)"> IDbContextOptionsExtension ApplyDefaults(IDbContextOptions options)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (ExecutionStrategyFactory == <span style="color: rgba(0, 0, 255, 1)">null</span>
&& (EngineType ==<span style="color: rgba(0, 0, 0, 1)"> SqlServerEngineType.AzureSql
</span>|| EngineType ==<span style="color: rgba(0, 0, 0, 1)"> SqlServerEngineType.AzureSynapse
</span>||<span style="color: rgba(0, 0, 0, 1)"> UseRetryingStrategyByDefault))
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> WithExecutionStrategyFactory(c => <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SqlServerRetryingExecutionStrategy(c));
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p>3、ApplyServices 方法:重点来了。对就是它,实现它,你就能向服务容器添加自定义的服务了。</p>
<p>4、Validate 方法:验证一下当前 DbContextOptions 的值是否符合你的要求,如果验证不通过,直接抛异常就行了。如果不需要验证,留空即可。</p>
<p> </p>
<p>现在咱们再来认识一下 DbContextOptionsExtensionInfo 抽象类。</p>
<p>1、LogFragment 属性:返回一个字符串,在记录日志时,这个字符串会出现在日志里。至于说是什么字符串,你可自己决定。</p>
<p>2、Extension 属性:返回与当前信息类相关的 IDbContextOptionsExtension 对象。</p>
<p>3、ShouldUseSameServiceProvider 方法:这个方法其实是在 DbContextOptions 类的 Equals 方法中作为判断两个 DbContextOptions 实例的配置是否相同的条件之一。意思就是如果结果是 true,表明所有配置相同的 DbContextOptions 不需要初始化新的服务容器。</p>
<p>4、GetServiceProviderHashCode 与 PopulateDebugInfo 方法:.NET CLR 对象的相等判断除了 Equals 方法,还有 GetHashCode 方法,看看是否返回相同的哈希。GetServiceProviderHashCode 方法你可以自定义返回的哈希值,DbContextOptions 类的 GetHashCode 方法中也调用了此方法。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> GetHashCode()
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> hashCode = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HashCode();
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="background-color: rgba(204, 255, 204, 1)"><span style="color: rgba(0, 0, 255, 1)">var</span> (type, value) <span style="color: rgba(0, 0, 255, 1)">in</span></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(204, 255, 204, 1)"> _extensionsMap</span>)
{
hashCode.Add(type);
hashCode.Add(<strong><span style="background-color: rgba(0, 255, 0, 1)">value.Extension.Info.GetServiceProviderHashCode()</span></strong>);
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> hashCode.ToHashCode();
}</span></pre>
</div>
<p>可见,如果 GetServiceProviderHashCode 方法返回的值改变,就会影响到 DbContextOptions 对象的相等判断。同样,GetServiceProviderHashCode 方法会和 PopulateDebugInfo 方法搭配用。PopulateDebugInfo 方法的参数是一个字典,开发者可以往里面设置一些自定义的 Key-Value 元素,这些元素通常表示当前扩展中被更改的值。</p>
<p>PopulateDebugInfo 方法中向字典添加的值也会被传递给 ServiceProviderDebugInfo 事件,事件相关的数据被封装到 ServiceProviderDebugInfoEventData 类中。</p>
<p>不过,但是,当你开启日志功能后,你会发现,ServiceProviderDebugInfo 事件根本不会输出到日志中。Github 上有人提过这事,但没人回答。在本文后面,老周会告诉你如何解决此问题。</p>
<p>----------------------------------------------------------------------------------------------------------------------------------------------------</p>
<p>上面是对知识点的简单理论介绍。说简单一点,就是你想扩展 EF Core,<strong><span style="color: rgba(0, 0, 255, 1)">就是实现 IDbContextOptionsExtension 接口,然后在 ApplyServices 方法中把你的服务放进容器</span></strong>。</p>
<p>理论总是抽象的,咱们动手练一练就好了。</p>
<p>第一步,老规矩,随便写个实体,然后从 DbContext 继承一个你的上下文。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Pet
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未知生物</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span>? Age { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContext : DbContext
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 公共属性:数据集</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> DbSet<Pet> Pets { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder opBuilder)
{
opBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=:memory:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开启日志</span>
.LogTo(log =><span style="color: rgba(0, 0, 0, 1)"> Console.WriteLine(log));
}
}</span></pre>
</div>
<p>第二步,既然咱们要扩展,当然要写服务类型了。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IHelloWorld
{
</span><span style="color: rgba(0, 0, 255, 1)">void</span> SayHello(<span style="color: rgba(0, 0, 255, 1)">string</span>?<span style="color: rgba(0, 0, 0, 1)"> who);
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoHelloWorld : IHelloWorld
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> SayHello(<span style="color: rgba(0, 0, 255, 1)">string</span>?<span style="color: rgba(0, 0, 0, 1)"> who)
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">你好,{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span>, who ?? <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">宇宙人</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p>当然了,通过构造函数的依赖注入,你是可以访问 EF Core 内部的服务的。这里为了演示的简单,就没有注入任何东西。</p>
<p>第三步,重点来了,实现那个,那个很辣眼睛的接口。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContextOptionsExtension : IDbContextOptionsExtension
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 扩展信息</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> MyExtInfo?<span style="color: rgba(0, 0, 0, 1)"> _info;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这个属性用于返回扩展信息。
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回的类型是 DbContextOptionsExtensionInfo,不需要访问 MyExtInfo 类</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <em><strong><span style="background-color: rgba(255, 255, 0, 1)">DbContextOptionsExtensionInfo Info => _info ??= <span style="color: rgba(0, 0, 255, 1)">new</span> MyExtInfo(<span style="color: rgba(0, 0, 255, 1)">this</span></span></strong></em><span style="color: rgba(0, 0, 0, 1)"><em><strong><span style="background-color: rgba(255, 255, 0, 1)">)</span></strong></em>;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ApplyServices(IServiceCollection services)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里,添加你的服务</span>
<em><strong><span style="background-color: rgba(255, 255, 0, 1)">services.AddScoped<IHelloWorld, DemoHelloWorld></span></strong></em><span style="color: rgba(0, 0, 0, 1)"><em><strong><span style="background-color: rgba(255, 255, 0, 1)">();</span></strong></em>
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Validate(IDbContextOptions options)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不需要验证,空方法</span>
<span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 一个随机整数,模拟选项改变</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> RandValue { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这个类私有化就可以了,因为对外公开的是 DbContextOptionsExtensionInfo 类型</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyExtInfo : DbContextOptionsExtensionInfo
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 构造函数</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> MyExtInfo(IDbContextOptionsExtension extension) : <span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">(extension)
{
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 替换一下基类成员,方便获取</span>
<span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">public</span> DemoDbContextOptionsExtension Extension => (DemoDbContextOptionsExtension)<span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.Extension;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 此处要返回 false,因为咱们这个不是数据库提供者</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsDatabaseProvider => <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 自定义日志输出</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">string</span> LogFragment => $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">这是个大扩展 - { Extension.RandValue.ToString()}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用 _myRandValue 的哈希,</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> GetServiceProviderHashCode()
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Extension.RandValue.GetHashCode();
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span> PopulateDebugInfo(IDictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">string</span>><span style="color: rgba(0, 0, 0, 1)"> debugInfo)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置调试信息</span>
debugInfo[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">MyExtension:RandomValue</span><span style="color: rgba(128, 0, 0, 1)">"</span>] =<span style="color: rgba(0, 0, 0, 1)"> Extension.RandValue.ToString();
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可以直接 retrun true</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> other <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> MyExtInfo;
}
}
}</span></pre>
</div>
<p>第四步,将自定义扩展添加到 DbContextOptions 的扩展集合中有点麻烦,所以一般要封装一个扩展方法,方便调用。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContextOptionsBuilderExtensions
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> DbContextOptionsBuilder UseDemoExt(
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)"> DbContextOptionsBuilder builder)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在添加前应当查找一下,避免重复添加</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> myext = builder.<span style="background-color: rgba(255, 255, 0, 1)">Options.FindExtension<DemoDbContextOptionsExtension></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)">()</span>;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果没有,就new一个</span>
myext ??= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContextOptionsExtension();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置一下随机数据,模拟配置改变</span>
myext.<span style="background-color: rgba(255, 255, 0, 1)">RandValue = Random.Shared.Next(<span style="color: rgba(128, 0, 128, 1)">100</span>, <span style="color: rgba(128, 0, 128, 1)">9999999</span></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)">)</span>;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加到扩展集合中(注意类型转换)</span>
<span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(204, 255, 204, 1)">((IDbContextOptionsBuilderInfrastructure)builder)</span></strong>.AddOrUpdateExtension(myext);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> builder;
}
}</span></pre>
</div>
<p><span style="color: rgba(0, 0, 128, 1)">a、要养成先找后加的习惯,即先 Find 一下扩展是不是已在集合中,然后再调用 AddOrUpdateExtension 方法添加到扩展集合中。</span></p>
<p><span style="color: rgba(0, 0, 128, 1)">b、由于此方法是显式实现了 IDbContextOptionsBuilderInfrastructure 接口,所以要先把 builder 转换为 IDbContextOptionsBuilderInfrastructure 接口类型再调用 AddOrUpdateExtension 方法。</span></p>
<p>第五步,回过头去修改 DemoDbContext 类。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContext : DbContext
{
……
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder opBuilder)
{
opBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=:memory:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开启日志</span>
.LogTo(log =><span style="color: rgba(0, 0, 0, 1)"> Console.WriteLine(log))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用自定义的扩展</span>
<span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">.UseDemoExt()</span></strong>;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 测试服务</span>
<span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Greeting(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> who)
</span></span> <span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(0, 0, 0, 1)">{
</span></span> <span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(0, 0, 0, 1)">IHelloWorld sv </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.GetService<IHelloWorld></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)">();
</span></span> <span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)">sv.SayHello(who);
</span></span> <span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)">}</span>
}</span></pre>
</div>
<p>第六步,实例化上下文对象,运行,实验一下。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> ctx = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContext();
ctx.Greeting(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">小王</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p> </p>
<p>运行结果如下图所示。</p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202510/367389-20251006113723847-83381930.png" alt="image" width="698" height="122" loading="lazy"></p>
<p> 很显然,ServiceProviderDebugInfo 事件没有日志输出的。</p>
<p> </p>
<p>现在,老周就说一下如何让它输出这个事件。</p>
<p>方法:使用 .NET Logging API。比如,咱们要使日志输出到控制台,需要添加 Microsoft.Extensions.Logging.Console 包的引用。</p>
<p>在上下文类中,定义 ILoggerFactory 类型的字段,并用 LoggerFactory.Create 方法创建实例。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContext : DbContext
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 静态成员</span>
<strong><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(0, 0, 255, 1)">static</span>ILoggerFactory logFac = LoggerFactory.Create(lb =></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)"> {
</span></strong></span> <span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">lb.AddConsole();
</span></strong></span> <span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">lb.SetMinimumLevel(LogLevel.Trace);
</span></strong></span> <span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">});</span></strong>
……
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder opBuilder)
{
opBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=:memory:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开启日志
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">.LogTo(log => Console.WriteLine(log))</span>
<strong><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">.UseLoggerFactory(logFac)
</span></strong> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用自定义的扩展</span>
<span style="color: rgba(0, 0, 0, 1)"> .UseDemoExt();
}
……
}</span></pre>
</div>
<p> SetMinimumLevel 方法将日志级别设置为 Debug 或 Trace。</p>
<p>在 OnConfiguring 方法中,使用 UseLoggerFactory 方法应用 LoggerFactory 对象。</p>
<p>修改之后,重新运行程序。结果如下。</p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202510/367389-20251006120444031-711545032.png" alt="image" width="723" height="204" loading="lazy"></p>
<p> </p>
<p>好了,今天就水到这里吧。</p><br><br>
来源:https://www.cnblogs.com/tcjiaan/p/19127370
頁:
[1]