EF Core与ASP.NET Core 的集成

目录

一、分层项目中EF Core的用法

二、使用“上下文池”时要谨慎

三、案例:批量注册上下文


一、分层项目中EF Core的用法

        在编写简单的演示案例的时候,我们通常会把项目的所有代码放到同一个文件夹中,而对于现实中比较复杂的项目,我们通常是要对其进行分层的,也就是不同的类放到不同的文件夹中。这样的分层项目中使用 EF Core 的时候有一些问题需要考虑。

        第1步,创建一个NET 类库项目,项目名字为 BooksEFCore。通过 NuGet 为项目安装MicrosoftEntityFrameworkCore.Relational包,并且在项目中增加代表图书的实体类 Book和它的实体类的配置类BookConfig,如下代码所示。

public record Book
{
public Guid Id{ get; set; }
public stringName{get; set;}
public double Price { get; set;}
}
class BookConfig : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.ToTable("T_Books");
}}

        这里,我们把 Book 类声明为一个记录类,而不是普通的类,主要是为了让编译器自动生成ToString 方法,帮我们简化对象的输出。

        第2步,在 BooksEFCore 项目中增加上下文类,如下代码所示

public class MyDbContext : DbContext
{
public DbSet<Book> Books { get;set;}
public MyDbContext(DbContextOptions<MyDbContext> options):base(options)
{}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}

        这里编写的MyDbContext和之前编写的上下文类不同。我们之前是重写OnConfiguring方法,在OnConfiguring方法中调用UseSqlServer 等方法来设置要使用的数据库。在实际项目中直接在OnConfiguring方法中硬编码要连接的数据库是不太合理的,因为我们可能需要在运行时通过读取配置来确定要连接的数据库,如果在上下文中硬编码了要连接的数据库,就会导致上下文复用性太差。因此我们尽量把上下文的数据库配置的代码写到ASPNET Core 项目中因此,在这里,我们没有重写 OnConfiguring 方法,而是为MyDbContext 类增加DbContextOptions<MyDbContex>类型参数的构造方法。DbContextOptions 是一个数据库连接配置对象,我们会在ASPNET Core 项目中提供对 DbContextOptions 的配置。

        第3步,创建一个ASPNET Core 项目,在这个项目中添加对 BooksEFCore 项目的引用。因为要连接SQLServer 数据库,所以我们通过 NuGet 安装 MicrosofEntityFramework Core.SqlServer。在ASP.NET Core 项目的 appsettings.json 中增加对数据连接字符的配置,如下代码所示。

"ConnectionStrings": {
"Default":"Server=.;Database=demo7;Trusted_Connection=True;"
}

        第4步,在 ASP.NET Core 项目的 Program.cs 的 builder Build0之前增加对上下文进行配置的代码如下所示。

builder.Services.AddDbContext<MyDbContext>(opt => {
string connStr = builder.Configuration.GetConnectionString("Default");
opt.UseSqlServer(connStr);
})

        使用AddDbContext方法来通过依赖注入的方式让MyDbContext采用我们指定的连接字符串连接数据库。由于 AddDbContext 方法是泛型的,因此我们可以为同一个项目中的多个不同的上下文设定连接不同的数据库。

        第5步,在ASPNET Core 项目中增加使用 MyDbContext 进行数据库读写的测试代码,如下代码所示。

public class TestController : Controller
{
private readonly MyDbContext dbCtx;
public TestController(MyDbContext dbctx)
{
this.dbCtx= dbCtx;
}
public async Task<IActionResult> Index()
{
dbCtx.Add(new Book { Id=Guid.NewGuid(),Name="C#",Price=59});
await dbCtx.SaveChangesAsync();
var book = dbCtx.Books.First();
return Content(book.Tostring()); ;
}
}

        由于在代码中采用依赖注入的形式配置并且注入了 MyDbContext,因此我们可以用依赖注入的形式来创建上下文,而不用像以前那样在代码中手动创建 MyDbContext 类的实体类。可以看到,依赖注入让代码的职责划分更加清晰。

        我们知道,如果一个被依赖注入容器管理的类实现了 IDisposable 接口,则离开作用域之后容器会自动调用对象的 Dispose方法。上下文是实现了Disposable接口的,因此注入的上文对象会被依赖注入容器正确地回收,开发人员一般不需要手动回收上下文对象。

        第6步,生成实体类的迁移脚本。在多项目的环境下执行 EF Core 的数据库迁有很多特殊的要求,稍不注意,在执行 Add-Migration 的时候,迁移工具就会提示“No DbContext was foundmassembly.” “Unable to create an obiect of type'MyDbContext.” 等错误。

        如果使用数据库迁移工具的时候出现这种错误,我们是可以通过研究数据库迁移工具的要求来调整代码来让它能够正常运行的,但是这个调整过程是非常麻烦的。因此,建议大家不要浪费时间在研究数据库迁移工具的配置上面,而是建议采用IDesignTimeDbContextFactory 接口来解决这个问题。

        当项目中存在一个IDesignTimeDbContextFactory 接口的实现类的时候,数据库迁移工具口来解决这个问题。就会调用这个实现类的CreateDbContext 方法来获取上下文对象,然后迁移工具会使用这个上下文对象来连接数据库。因此,我们需要在 BooksEFCore 项目中创建一个IDesignTimeDbContextFactory 接口的实现类,如下代码所示。

class MyDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder<MyDbContext> builder = new ();
string connStr = Environment.GetEnvironmentVariable("ConnectionStrings;BooksEFCore");
builder.UseSqlserver(connStr);
return new MyDbContext(builder.Options);
}}

        我们可以用硬编码的方式把连接配置信息写到代码中,因为这个代码只有在开发环境下才会运行。如果我们不希望数据库的连接字符串被写到项目的代码中,我们也可以把连接字符串配置到环境变量中,不过 MyDesignTimeDbContextFactory 中很难使用IConfiguration 等来读取NET的配置,我们可以直接调用 Environment.GetEnvironmentVariable 来读取环境变量,如第6行代码所示。我们要在环境变量中增加一个名字为 ConnectionStrings:BooksEFCore 的项,其值为数据库的连接字符串。

        因为数据库迁移脚本要生成到 BooksEFCore 项目中,所以我们为这个项目安装Microsoft.EntityFrameworkCore.Tools、Microsof.EntityFrameworkCore.SqlServer 这两个程序集,然后把 BooksEFCore 设置为启动项目,并且在[程序包管理器控制台]中选中 BooksEFCore项目后,执行 Add-Migration Init 生成数据库迁移脚本,然后执行 Update-database 命令即可完成数据库的创建。

        接下来启动 ASP.NET Core 项目,我们就可以看到序能够正常地执行了。我们向Test/Index 发送请求,就可以看到程序运行成功,数据库中也正常地插入了数据。

        综上所述,在分层项目中,我们把实体类、上下文写到独立于 ASP.NET Core 的项目中把数据库连接的配置使用依赖注入的方式写到 ASP.NET Core 项目中,这样就做到了项目职责的清晰划分。

二、使用“上下文池”时要谨慎

        上下文被创建的时候不仅要创建数据库连接,而且要执行实体类的配置等,因此实例化上下文的时候会消耗较多的资源。为了避免性能损失,EF Core 中提供了可以用来代替AddDbContext 的 AddDbContextPol来注入上下文。对于使用 AddDbContextPool注入的上下文,EF Core 会优先从“上下文池”中获取实例,当一个上下文不再被使用后,会被返回上下文池,而不会被销毁。因此,使用上下文池能够在一定程度上提升程序的性能。不过,使用AddDbContextPool时也有一些需要注意的问题。

        首先,使用 AddDbContext 的时候,我们可以为上下文注入服务;但是在使用AddDbContextPool的时候,由于上下文实例会被复用,因此我们无法为上下文注入服务。

        其次,很多数据库的ADO.NET 提供者都实现了“数据库连接池”机制,由于 EF Core 是基于ADO.NET的,因此 EF Core 也自然可以使用数据库连接池。但是上下文池和数据库连接池的共存如果处理不当就会引起问题,对这个感兴趣的读者可以去网上搜索AddDbContextPool 连接池耗尽”。

        在进行项目开发时,推荐开发人员采用“小上下文”策略,也就是不要把项目中所有的实体类都放到同一个上下文类中,而是只把关系紧密的实体类放到同一个上下文类中,把关系不紧密的实体类放到不同的上下文类中。也就是项目中存在多个上下文类,每个上下文类中只有少数几个实体类。如果采用这样的小上下文策略,那么一个上下文实例初始化的时候,实体类的配置等过程将非常快,其不会成为性能瓶颈,而且如果启用了数据库连接池,数据库连接的创建也不会成为性能瓶颈。

        总之,如果项目中需要为上下文注入其他服务,则不能使用 AddDbContextPool; 如果项目中采用小上下文策略,并且启用了数据库连接池的话,一般也不需要使用AddDbContextPool。

三、案例:批量注册上下文

        如果项目采用小上下文策略,在项目中可能就存在着多个上下文类,我们需要手动为这些项目调用AddDbContext方法进行注册,显然这比较麻烦。

        如果这些上下文连接的都是相同的数据库的话,我们可以采用反射的方式扫描程序集中所有的上下文类,然后为它们逐个调用AddDbContext 注册,如下代码所示。

        

public static IServiceCollection AddAllDbContexts(this IServiceCollection services,
Action<DbContextOptionsBuilder> builder,IEnumerable<Assembly> assemblies)
{
Type[] types = new Type[] { typeof(IServiceCollection),
typeof(Action<DbContextOptionsBuilder>),
typeof(ServiceLifetime),typeof(ServiceLifetime) };
var methodAddDbContext = typeof(EntityFrameworkServiceCollectionExtensions).GetMethod("AddDbContext",1,types);
foreach (var asmToLoad in assemblies)
{
foreach (var dbCtxType in asmToLoad.GetTypes().Where(t => !t.IsAbstract && typeof(DbContext).IsAssignableFrom(t)))
{
var methodGenericAddDbContext = methodAddDbContext.MakeGenericMethod(dbctxType);
methodGenericAddDbContext.Invoke(null,new object[] { services,builder,ServiceLifetime.Scoped,ServiceLifetime.Scoped });
}
}
return services;
}

        其中,builder 参数是对上下文的连接字符串等进行配置的回调方法,而assemblies 参数则为所有含有上下文类的程序集。在第 7行代码中通过反射获得 AddDbContext 方法,然后在第10行代码中通过反射获得程序中所有非抽象的上下文类,这里我们使用 GetTypes而非GetExportedTypes方法来获得程序中的类,因为考虑到有的项目中会把上下文的访问修饰符设置为 internal。在第 12~13代码中通过反射调用 AddDbContext 方法,由于AddDbContext方法是泛型的,因此我们要先使用 MakeGenericMethod 方法设定泛型的类型,然后才能调用AddDbContext 方法。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 什么是ASP.NET CoreASP.NET Core是一个跨平台的开源Web应用程序框架,它可以在Windows、Linux和macOS等操作系统上运行。它是.NET Core的一部分,可以使用C#、F#和Visual Basic等语言进行开发。 2. ASP.NET CoreASP.NET有什么区别? ASP.NET Core是一个轻量级的框架,它比ASP.NET更加灵活和可扩展。它可以在不同的操作系统上运行,而ASP.NET只能在Windows上运行。ASP.NET Core还提供了更好的性能和安全性,以及更好的支持Docker和云计算等新技术。 3. 什么是中间件? 中间件是ASP.NET Core应用程序中的一个组件,它可以处理HTTP请求和响应。中间件可以是一个单独的函数或一个类,它可以在请求到达应用程序之前或之后执行一些操作,例如记录日志、验证身份、压缩响应等。 4. 什么是依赖注入? 依赖注入是一种设计模式,它可以帮助我们解耦应用程序中的组件。在ASP.NET Core中,我们可以使用依赖注入来管理应用程序中的服务和组件。依赖注入可以使代码更加可测试、可维护和可扩展。 5. 什么是Razor视图引擎? Razor视图引擎是ASP.NET Core中的一个组件,它可以帮助我们创建动态的HTML页面。Razor视图引擎使用C#或VB.NET等语言来编写视图代码,它还提供了一些特殊的语法和标记,例如@符号、@model指令、@if语句等。 6. 什么是MVC模式? MVC模式是一种软件架构模式,它将应用程序分为三个部分:模型、视图和控制器。模型表示应用程序的数据和业务逻辑,视图表示应用程序的用户界面,控制器负责处理用户请求并更新模型和视图。 7. 什么是Web API? Web API是一种用于创建RESTful Web服务的框架。在ASP.NET Core中,我们可以使用Web API来创建基于HTTP协议的API,它可以返回JSON、XML等格式的数据。Web API还提供了一些特殊的特性,例如路由、过滤器、模型绑定等。 8. 什么是SignalR? SignalR是ASP.NET Core中的一个组件,它可以帮助我们创建实时Web应用程序。SignalR使用WebSocket协议或其他技术来实现双向通信,它可以在服务器和客户端之间传输数据和消息。 9. 什么是Identity框架? Identity框架是ASP.NET Core中的一个组件,它可以帮助我们管理应用程序中的用户和身份验证。Identity框架提供了一些特殊的类和方法,例如UserManager、SignInManager、IdentityRole等,它还可以与ASP.NET Core中的其他组件集成,例如中间件、授权等。 10. 什么是EF CoreEF CoreASP.NET Core中的一个组件,它可以帮助我们管理应用程序中的数据访问。EF Core是Entity Framework的轻量级版本,它可以在不同的数据库上运行,例如SQL Server、MySQL、PostgreSQL等。EF Core提供了一些特殊的类和方法,例如DbContextDbSet、LINQ查询等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咬口大葱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值