当前位置: 首页 > news >正文

资讯型电商网站优缺点工作总结写作

资讯型电商网站优缺点,工作总结写作,有那种做拼贴的网站吗,科普互联网站建设文章目录前言一、EF Core的DbContext默认生命周期二、单次HTTP请求中DbContext的状态2.1 准备工作2.2 DbContext类型2.3 注册服务到DI容器2.4 依赖DbContext的测试服务2.5 Controller里HTTP请求注入DbContext2.6 更改依赖DbContext测试服务的生命周期三、结论前言 我们在ASP.N…

文章目录

  • 前言
  • 一、EF Core的DbContext默认生命周期
  • 二、单次HTTP请求中DbContext的状态
    • 2.1 准备工作
    • 2.2 DbContext类型
    • 2.3 注册服务到DI容器
    • 2.4 依赖DbContext的测试服务
    • 2.5 Controller里HTTP请求注入DbContext
    • 2.6 更改依赖DbContext测试服务的生命周期
  • 三、结论


前言

我们在ASP.NET Core中使用EF Core都是通过在Services上AddDbContext的形式将其注册到DI容器里。并且还会有很多数据服务依赖于DbContext,这些依赖服务也是需要注册到DI容器里。

每次我在ASP.NET Core中使用EF Core都会很好奇,一次HTTP请求中,这些DbContext和依赖其的数据服务是如何被注入进去的。重复注入的情况下,什么情况下只会创建一次,什么情况下创建多次。其实这也就是关于DbContext生命周期的一个讨论。

本文将探讨通过DI注入EF Core的DbContext在HTTP请求中的生命周期,并且分析这样设计的原因。

这里我先简单给出结论,ASP.NET Core中默认的DbContext是Scoped的生命周期,一次HTTP请求对应着一个Scoped。换言之,在一个HTTP请求里,通过DI注入的DbContext都是同一个实例。


一、EF Core的DbContext默认生命周期

.NET9里的Program.cs,是通过AddDbContext的形式注入DbContext。其实这里的AddDbContext是一个名为EntityFrameworkServiceCollectionExtensions的静态扩展方法,在Microsoft.Extensions.DependencyInjection命名空间下。

不知道大家是否好奇DbContext的生命周期是什么,其实源码里已经给出了答案。

Pragram.cs

builder.Services.AddDbContext<MyDbContext>(opt =>
{string conn = builder.Configuration["ConnectionStrings:MySQL"];opt.UseMySQL(conn);
});

以下两个AddDbContext方法都是Entity Framework Core中用于向依赖注入容器注册数据库上下文的扩展方法。前者只接受一个泛型参数 TContext,它既是服务类型也是实现类型;后者接受两个泛型参数,TContextService 是服务接口 / 基类,TContextImplementation 是具体实现类,满足注册一个抽象服务类型和其具体实现时使用。

我们观察到参数ServiceLifetime contextLifetime = ServiceLifetime.Scoped,也就是说在调用AddDbContext时候,如果未指定参数,默认DbContext的生命周期为Scoped。

AddDbContext源码

public static IServiceCollection AddDbContext<[DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContext>(this IServiceCollection serviceCollection,Action<DbContextOptionsBuilder>? optionsAction = null,ServiceLifetime contextLifetime = ServiceLifetime.Scoped,ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)where TContext : DbContext=> AddDbContext<TContext, TContext>(serviceCollection, optionsAction, contextLifetime, optionsLifetime);
public static IServiceCollection AddDbContext<TContextService, [DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContextImplementation>(this IServiceCollection serviceCollection,Action<DbContextOptionsBuilder>? optionsAction = null,ServiceLifetime contextLifetime = ServiceLifetime.Scoped,ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)where TContextImplementation : DbContext, TContextService=> AddDbContext<TContextService, TContextImplementation>(serviceCollection,optionsAction == null? null: (_, b) => optionsAction(b), contextLifetime, optionsLifetime);

好了,我们知道了EF Core的DbContext默认生命周期为Scope。现在需要明确的是一次HTTP是否对应着一个Scope,注册Scoped生命周期的DbContext是否是共享同一个实例。

二、单次HTTP请求中DbContext的状态

2.1 准备工作

在一次HTTP请求中,我们可以通过观察DbContext实例后的对象来间接观察DbContext的状态。也就是说如果单次HTTP请求中DbContext只被实例化了一次,那就说明一次HTTP请求中只会注入同一个DbContext。
为了确认DbContext实例化后对象,我们在DbContext里创建一个实例ID,用来分辨实例化后的对象。
实例的唯一ID

public Guid InstanceId { get; } = Guid.NewGuid();

ASP.NET Core中往DI注册的服务生命周期分三种,为Scoped、Transient 和 Singleton。为了方便测试一次HTTP请求中DbContext的状态,我们接下来我们往Program.cs注册三种生命周期的DbContext。

并且在Controllerl里除了通过构造函数注入DbContext,还通过服务定位器模式从DI容器里再次获取DbContext实例,来判断二者是否相同。

在控制器中通过服务定位器模式第二次获取各种DbContext

var scopedDb2 = _serviceProvider.GetRequiredService<ScopedDbContext>();
var transientDb2 = _serviceProvider.GetRequiredService<TransientDbContext>();
var singletonDb2 = _serviceProvider.GetRequiredService<SingletonDbContext>();

最后我们再注册一个依赖DbContext的服务,在里面再次通过构造函数注入DbContext。
依赖DbContext的服务

 public class TestService{public ScopedDbContext ScopedDb { get; }public TransientDbContext TransientDb { get; }public SingletonDbContext SingletonDb { get; }public TestService(ScopedDbContext scopedDb,TransientDbContext transientDb,SingletonDbContext singletonDb){ScopedDb = scopedDb;TransientDb = transientDb;SingletonDb = singletonDb;}}

2.2 DbContext类型

建立三种DbContext,用于对应注册三种生命周期。并且通过基类分配一个用于初始化的ID。
BaseDbContext.cs

// 测试用的DbContext基类
public abstract class BaseDbContext : DbContext
{public Guid InstanceId { get; } = Guid.NewGuid();public BaseDbContext(DbContextOptions options) : base(options) { }
}

TransientDbContext.cs

public class TransientDbContext : BaseDbContext
{public TransientDbContext(DbContextOptions<TransientDbContext> options) : base(options) { }
}

ScopedDbContext.cs

public class ScopedDbContext: BaseDbContext
{public ScopedDbContext(DbContextOptions<ScopedDbContext> options) : base(options) { }
}

SingletonDbContext.cs

public class SingletonDbContext : BaseDbContext
{public SingletonDbContext(DbContextOptions<SingletonDbContext> options) : base(options) { }
}

2.3 注册服务到DI容器

在Program文件里注册三种DbContext,和依赖DbContext的测试服务。
Program.cs

// 1. 注册Scoped生命周期的DbContext
builder.Services.AddDbContext<ScopedDbContext>(options =>{string conn = builder.Configuration["ConnectionStrings:MySQL"];options.UseMySQL(conn);},ServiceLifetime.Scoped
);// 2. 注册Transient生命周期的DbContext
builder.Services.AddDbContext<TransientDbContext>(options =>{string conn = builder.Configuration["ConnectionStrings:MySQL"];options.UseMySQL(conn);},ServiceLifetime.Transient
);// 3. 注册Singleton生命周期的DbContext
builder.Services.AddDbContext<SingletonDbContext>(options =>{string conn = builder.Configuration["ConnectionStrings:MySQL"];options.UseMySQL(conn);},ServiceLifetime.Singleton
);// 注册测试服务
builder.Services.AddScoped<TestService>();

2.4 依赖DbContext的测试服务

建立一个依赖DbContext的测试服务,同时也注入三种DbContext。
依赖DbContext的服务

 public class TestService{public ScopedDbContext ScopedDb { get; }public TransientDbContext TransientDb { get; }public SingletonDbContext SingletonDb { get; }public TestService(ScopedDbContext scopedDb,TransientDbContext transientDb,SingletonDbContext singletonDb){ScopedDb = scopedDb;TransientDb = transientDb;SingletonDb = singletonDb;}}

2.5 Controller里HTTP请求注入DbContext

ASP.NET Core中Controller是服务器端用于接收、处理这些请求的 “逻辑容器”,是处理请求的核心组件。每次请求,路由中间件在请求管道中负责路由匹配的核心组件,最后匹配到Controller里的Action上。

这里我们通过在Controller的构造函数注入三种生命周期的DbContext,依赖DbContext的测试服务和一个ServiceProvider。

其中ServiceProvider是为了通过服务定位器模式从DI里再次获取三种生命周期的DbContext。这样我们就能模拟出DbContext通过DI容器在控制器里三次注入DbContext的情况。

[Route("api/[controller]/[action]")]
[ApiController]
public class MovieController : ControllerBase
{private readonly ScopedDbContext _scopedDb1;private readonly TransientDbContext _transientDb1;private readonly SingletonDbContext _singletonDb1;private readonly TestService _testService;private readonly IServiceProvider _serviceProvider;public MovieController(ScopedDbContext scopedDb1, TransientDbContext transientDb1, SingletonDbContext singletonDb1, TestService testService, IServiceProvider serviceProvider){_scopedDb1 = scopedDb1;_transientDb1 = transientDb1;_singletonDb1 = singletonDb1;_testService = testService;_serviceProvider = serviceProvider;}public ActionResult TestLifeCycle(){// 在控制器中通过服务定位器模式第二次获取各种DbContextvar scopedDb2 = _serviceProvider.GetRequiredService<ScopedDbContext>();var transientDb2 = _serviceProvider.GetRequiredService<TransientDbContext>();var singletonDb2 = _serviceProvider.GetRequiredService<SingletonDbContext>();var result = new StringBuilder();result.AppendLine("=== 单次HTTP请求中不同生命周期DbContext测试 ===");result.AppendLine();// 测试Scoped DbContextresult.AppendLine("1. Scoped DbContext:");result.AppendLine($"   控制器直接注入实例ID: {_scopedDb1.InstanceId}");result.AppendLine($"   服务中注入实例ID: {_testService.ScopedDb.InstanceId}");result.AppendLine($"   第二次获取实例ID: {scopedDb2.InstanceId}");result.AppendLine($"   同一请求内是否相同: {_scopedDb1.InstanceId == _testService.ScopedDb.InstanceId && _scopedDb1.InstanceId == scopedDb2.InstanceId}");result.AppendLine();// 测试Transient DbContextresult.AppendLine("2. Transient DbContext:");result.AppendLine($"   控制器直接注入实例ID: {_transientDb1.InstanceId}");result.AppendLine($"   服务中注入实例ID: {_testService.TransientDb.InstanceId}");result.AppendLine($"   第二次获取实例ID: {transientDb2.InstanceId}");result.AppendLine($"   同一请求内是否相同: {_transientDb1.InstanceId == _testService.TransientDb.InstanceId && _transientDb1.InstanceId == transientDb2.InstanceId}");result.AppendLine();// 测试Singleton DbContextresult.AppendLine("3. Singleton DbContext:");result.AppendLine($"   控制器直接注入实例ID: {_singletonDb1.InstanceId}");result.AppendLine($"   服务中注入实例ID: {_testService.SingletonDb.InstanceId}");result.AppendLine($"   第二次获取实例ID: {singletonDb2.InstanceId}");result.AppendLine($"   所有地方是否相同: {_singletonDb1.InstanceId == _testService.SingletonDb.InstanceId && _singletonDb1.InstanceId == singletonDb2.InstanceId}");return Content(result.ToString(), "text/plain", System.Text.Encoding.UTF8);}
}

然后不停刷新请求,我们观察到:在单次HTTP请求中被注册为Scoped的DbContext,无论控制器通过DI注入了多少次,得到的还是同一个实例。而Transient的DbContext,每次通过DI注入,获得的都是新的实例。最后是Singleton的DbContext,从服务启动开始,一直维持同一个实例。
执行结果

=== 单次HTTP请求中不同生命周期DbContext测试 ===1. Scoped DbContext:控制器直接注入实例ID: 1bd4f1df-8c03-433a-96b8-8c65a56fbf07服务中注入实例ID: 1bd4f1df-8c03-433a-96b8-8c65a56fbf07第二次获取实例ID: 1bd4f1df-8c03-433a-96b8-8c65a56fbf07同一请求内是否相同: True2. Transient DbContext:控制器直接注入实例ID: 75aaf5d5-b474-4bd4-86f9-bfe2333fa3fa服务中注入实例ID: 4a62dd59-a6cc-4d1a-a799-0b1aba97398f第二次获取实例ID: d4513e70-12e4-40fc-8363-42ab5f93d80f同一请求内是否相同: False3. Singleton DbContext:控制器直接注入实例ID: 67806fd0-58ae-45fc-8a52-0746712b6e9d服务中注入实例ID: 67806fd0-58ae-45fc-8a52-0746712b6e9d第二次获取实例ID: 67806fd0-58ae-45fc-8a52-0746712b6e9d所有地方是否相同: True

2.6 更改依赖DbContext测试服务的生命周期

受限于依赖注入的原则,长生命周期的服务不能依赖于短生命周期的服务。所以这里的DbContext有且只能选择Scoped和Transient,注册Singleton运行时会异常报错。接下来我们测试Transient。

// 注册测试服务
builder.Services.AddTransient<TestService>();

执行结果其实并未有改变,这是因为虽然依赖DbContext的测试服务被注册为Transient,每次都是通过DI注入的一个全新的实例,但是就DbContext本身,还是取决于DbContext被注册的自己的生命周期。

换句话说TestService 的作用只是 “传递” 它所依赖的DbContext实例,而不是 “决定” 这些DbContext 的生命周期。

这样得出一个结论,像我们平常用到的数据库服务类,如果被注册了Transient。也仅仅是DI注入的时候会创建实例,不影响DbContext 。

执行结果

=== 单次HTTP请求中不同生命周期DbContext测试 ===1. Scoped DbContext:控制器直接注入实例ID: 10954500-fb13-4bb4-9551-3f27adf2a993服务中注入实例ID: 10954500-fb13-4bb4-9551-3f27adf2a993第二次获取实例ID: 10954500-fb13-4bb4-9551-3f27adf2a993同一请求内是否相同: True2. Transient DbContext:控制器直接注入实例ID: 1cdad0b5-3a9f-4d18-9471-150528cb9d34服务中注入实例ID: 3f70152d-156b-4d8e-9cbd-e35ca500b9cd第二次获取实例ID: 49ce5f69-f8ab-458e-b367-dc6a58ec6107同一请求内是否相同: False3. Singleton DbContext:控制器直接注入实例ID: c409c795-085b-424f-aba7-d23deab80180服务中注入实例ID: c409c795-085b-424f-aba7-d23deab80180第二次获取实例ID: c409c795-085b-424f-aba7-d23deab80180所有地方是否相同: True

三、结论

至此,总结为以下几点内容:

  1. 通过Scoped 注册。同一HTTP请求内,无论在哪里获取DbContext,都是同一个实例。保证了单次请求中实体跟踪和事务等操作中数据操作的一致性,在请求结束后自动释放资源。并且依赖DbContext 的服务也是注册为Scoped 最佳。这是最为推荐的方式。
  2. 通过Transient注册。同一HTTP请求内,每次获取都会创建新的DbContext实例。这会导致实体状态无法共享,出现修改的数据不同步。并且会频繁创建和销毁实例。
  3. 通过Singleton注册。整个应用生命周期内只有一个实例,所有请求和服务共享。这样会导致多请求并发操作时会导致数据混乱和异常,线程不安全,并且出现内存泄漏的问题。要极力避免。
http://www.dtcms.com/a/460312.html

相关文章:

  • wordpress定时网站地图阜阳学校网站建设
  • 易企网站建设网站seo其应用
  • 域名做网站名网站建设 案例展示
  • 西安做网站公司xamokj怎么做网页小精灵
  • 地方门户网站管理系统做化工的外贸网站都有什么地方
  • 网站建设优化佛山网站注册页面
  • 朝阳网站建设wordpress 列表页输出
  • 银川网站建设一条龙服务网站怎么做才 吸引人
  • 为什么自己做不出一个好网站手机编程软件中文版免费
  • 萝岗门户网站建设公众号怎么运营
  • 成都网站建设优惠活动东莞找网站设计
  • 丹阳网站建设如何WordPress微信强制跳转插件
  • 无锡网站制作企业中文wordpress主题下载地址
  • 视频网站直播怎么做的城市生活网官方网站app
  • 南宁网站定制公司推广之家
  • 自己建网站流程要学什么上海猎头公司名录
  • 免费网站推广怎么做个人网页设计ps
  • 什么是网站app建设台州建设信息网站
  • 网站价格怎么样给公司做网站
  • 公司做网站百度可以搜到吗wordpress数据库连接
  • 广西网站建设建议公司官网登录入口
  • 如何评价一个网站amp网站建设
  • 绍兴的网站建设公司中山手机台app
  • 网站规划和建设的基本要求成都网站托管外包
  • 做网站 搞流量网站推广是什么意思
  • 中国营销网官网WordPress优化百度广告
  • 上海市建设安全协会 - 网站首页在哪里做百度网站
  • 网站点击按钮回到页面顶部怎么做收录优美图片
  • 网站制作 广州网站开发的实训周的实训过程
  • 织梦网站维护备案的网站有什么好处