配置实体框架DbContext的可扩展方案
介绍
在ASP.NET Core Web应用程序中配置DbContext时,通常使用如下扩展方法:AddDbContext
services.AddDbContext<xxxDbContext>(dbContextOptionsBuilder =>dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("The name of the connection string in the configuration file.")));
// Or
services.AddDbContext<xxxDbContext>((serviceProvider, dbContextOptionsBuilder) =>
{var service = serviceProvider.GetService<xxx>();dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("The name of the connection string in the configuration file.");
});
如果我们仔细研究AddDbContext 扩展方法的参数,我们会发现它是一个action,通过Action,我们可以封装一个方法、委托、内联委托、lambda等。
在这种情况下,Action必须构造DbContext选项。
我们最感兴趣的是根据我们的配置' {environment} settings.json '配置DbContext。
我们如何实现这种情况?为什么?
我们为什么要这样做的问题的答案:
我们想极大地简化和改善配置DbContext的过程,并使它们真正可组合,在这里我们可以尝试创建一个自动配置DbContext的灵活方案,以便它可以封装完整的功能。并提供即时实用程序,而不必经历如何在Startup配置类的不同位置手动配置它的各个步骤。
从这一点开始,我们将尝试弄清楚如何实现此方案,并尝试简化我们将遇到的所有概念。
使用代码
启动类
我们将尝试从主类开始,通过它可以开始配置我们正在处理的程序。
绝对是一个Startup类,我事先知道,每个编写ASP.NET Core代码的人都熟悉它,并且知道他们在做什么,但是让我们以一种简单快速的方式来克服它。
每个ASP.NET Core应用程序必须具有自己的配置代码,以配置应用程序的服务并创建应用程序的请求处理管道。
我们可以通过两种不同的方式来做到这一点:
通过在宿主构建器上调用ConfigureServices和Configure便捷方法。
public class Program
{public static void Main(string[] args){CreateHostBuilder(args).Build().Run();}public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureAppConfiguration((hostingContext, config) => { }).ConfigureWebHostDefaults(webBuilder =>{webBuilder.ConfigureServices(services =>{services.AddControllersWithViews();// ...}).Configure(app =>{if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{// ...}// ...});});
}
2.使用Startup配置服务
Startup类名是由ASP.NET Core约定的,我们可以为Startup类提供任何名称。
可选的,Startup类有两个方法,ConfigureServices方法告诉ASP.NET Core,其功能是提供和配置方法,告诉它如何使用它。
public class Startup
{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }public void ConfigureServices(IServiceCollection services){services.AddRazorPages();// ...}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Error");app.UseHsts();}//...}
}
当我们启动ASP.NET Core应用程序时,ASP.NET Core将创建Startup类的新实例,并调用ConfigureServices 方法来创建其服务。然后,它调用Configure 方法,该方法设置请求管道以处理传入的HTTP请求。
Startup类通常通过调用指定WebHostBuilderExtensions.UseStartup <TSTARTUP>宿主构建器方法:
public class Program
{public static void Main(string[] args){BuildWebHost(args).Run();}public static IWebHost BuildWebHost(string[] args){WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build();}
}
因此,我们程序中的Startup类最初将如下所示:
public class Startup
{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }public void ConfigureServices(IServiceCollection services){// ...services.AddDbContext<xxxDbContext>(dbContextOptionsBuilder =>dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("The name of the connection string in the configuration file.")));//Orservices.AddDbContext<xxxDbContext>((serviceProvider, dbContextOptionsBuilder) =>{var service = serviceProvider.GetService<xxx>();dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("The name of the connection string in the configuration file."));});// ...}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}//...}
}
但是,这样的DbContext 会在小型程序中找到,有时在中等程序中也会出现,但是在大型程序中,将很难用一个DbContext来管理该程序。
我们将看到我们必须将单个DbContext拆分为服务于特定上下文的多个DbContext,因此我们将拥有一组我们总是希望在所有开发环境中进行配置的DbContext,有时我们必须考虑到这一点我们还更改了实体框架提供程序。
最初,您会认为问题很简单,就是更改ConnectionString,一切都会好起来的,这也是我们还将做的,但是以另一种方式,我们将在稍后看到,但此刻,请不要忘记我们前面说过的话,有时我们会更改数据实体框架提供者。
让我们举一个例子来说明测试环境中的问题。通常,我们需要将Entity Framework提供程序更改为InMemory提供程序或Sqlite提供程序,并且在Sqlite提供程序中,InMemory或系统文件中存在两种可能的情况。
但是,这些提供程序不是唯一的,但更常用于SQL。
现在,让我们脱离这一上下文,并讨论设计如此大的DbContext的最重要模式,即边界上下文。
域驱动的设计—边界上下文
根据Martin Fowler的说法:
边界上下文是域驱动设计中的中心模式。DDD战略设计部分的重点是与大型模型和团队打交道。DDD通过将大型模型划分为不同的边界上下文并明确说明它们之间的相互关系来处理它们。
根据朱莉·勒曼(Julie Lerman)的说法:
当您使用大型模型和大型应用程序时,设计针对特定应用程序任务的较小,更紧凑的模型有许多好处,而不是为整个解决方案使用单一模型。在本专栏中,我将向您介绍域驱动设计(DDD)的概念——边界上下文——并向您展示如何将其应用到EF中以建立目标模型,重点是通过EF的更大灵活性来做到这一点。代码优先功能。如果您不熟悉DDD,即使您不完全致力于DDD,这也是一种学习的好方法。而且,如果您已经在使用DDD,则可以通过遵循DDD做法了解如何使用EF来受益。
根据这种模式,我们必须将单个DbContext拆分为多个DbContext,我们的新Startup类将变为:
public class Startup
{// ...public void ConfigureServices(IServiceCollection services){// ...services.AddDbContextPool<DbContextA>(dbContextOptionsBuilder =>dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("The name of the connection string in the configuration file."))).AddDbContextPool<DbContextB>(dbContextOptionsBuilder =>dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("The name of the connection string in the configuration file."))).AddDbContextPool<DbContextC>(dbContextOptionsBuilder =>dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("The name of the connection string in the configuration file.")));// ...}// ...
}
DbContextConfigurer模式
从上面的内容中我们将注意到,任何小的更改都可能导致代码中的重大修改。
因此,现在,我们将使用一些设计模式和OOP原理,并创建一些类来帮助我们简化此过程。
选项(Options)模式
为了使我们的应用程序设置更有条理,并提供对相关设置组的强类型访问,我们将使用ASP.NET Core中的Options模式。
我们将创建两个新类来配置连接字符串设置和实体框架提供程序设置:
public class ConnectionStringsOptions : IOptions<ConnectionStringsOptions>
{public const string KEY_NAME = "ConnectionStringsOptions";public ConnectionStringsOptions() : this(null, null, null, null) { }public ConnectionStringsOptions(string serverName, string databaseName,string userId, string password){ServerName = serverName;DatabaseName = databaseName;UserId = userId;Password = password;}public string ServerName { get; set; }public string DatabaseName { get; set; }public string UserId { get; set; }public string Password { get; set; }ConnectionStringsOptions IOptions<ConnectionStringsOptions>.Value => this;
}public static class EntityFrameworkProviders
{public static string SqlServer = "SQL-SERVER";public static string SQLite = "SQLITE";public static string InMemor = "IN-MEMOR";
}public class EntityFrameworkOptions : IOptions<EntityFrameworkOptions>
{public const string KEY_NAME = "EntityFrameworkOptions";public EntityFrameworkOptions() : this(EntityFrameworkProviders.SqlServer, true) { }public EntityFrameworkOptions(string provider, bool canMigrate){Provider = provider;CanMigrate = canMigrate;}public string Provider { get; set; }/// <summary>/// In some providers, we must not execute migration/// </summary>public bool CanMigrate { get; set; }EntityFrameworkOptions IOptions<EntityFrameworkOptions>.Value => this;
}
要使用当前结构,我们必须对appsettings.json和appsettings.Development.json进行一些更改。如下所示:
// appsettings.json
{"EntityFrameworkOptions": {"Provider": "SQL-SERVER","CanMigrate": true},"ConnectionStringsOptions": {"ServerName": "xxx.database.windows.net","DatabaseName": "xxx","UserId": "xxx_Developers","Password": "xxxx-xxx-xxx-xxx"}
}
//appsettings.Development.json
{"EntityFrameworkOptions": {"Provider": "SQLITE","CanMigrate": true},"ConnectionStringsOptions": {"ServerName": null,"DatabaseName": "dev.db","UserId": null,"Password": null}
}
当我们需要访问强类型设置时,我们只需要将IOptions <>类的实例注入到我们消费类的构造函数中,然后让依赖项注入处理其余部分:
using System.Collections.Generic;
using ConfigureEFDbContext.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;namespace ConfigureEFDbContext.Controllers
{[ApiController][Route("[controller]")]public class OptionsPatternController : ControllerBase{private readonly EntityFrameworkOptions _entityFrameworkOptions;private readonly ConnectionStringsOptions _connectionStringsOptions;public OptionsPatternController(IOptions<EntityFrameworkOptions>entityFrameworkOptions, IOptions<ConnectionStringsOptions> connectionStringsOptions){_entityFrameworkOptions = entityFrameworkOptions.Value;_connectionStringsOptions = connectionStringsOptions.Value;}[HttpGet]public IEnumerable<string> Get() => new[] { _entityFrameworkOptions.Provider,_connectionStringsOptions.DatabaseName };}
}
DbContext配置工厂模式
因为在配置DbContext时需要在代码中提供高度的灵活性,并且需要将对象的构造与对象本身分开,所以我们使用Factory Pattern。
根据维基百科
在基于类的编程中,工厂方法模式是一种创建模式,该模式使用工厂方法来处理创建对象的问题,而不必指定将要创建的对象的确切类。这是通过调用工厂方法来创建对象的,而不是通过调用构造函数,该方法在接口中指定并由子类实现,或者在基类中实现,并且可以选择由派生类覆盖。
该IDbContextConfigurerFactory是Factory接口并且DbContextConfigurerFactory是Factory的实现。
using System;
using System.Collections.Generic;
using System.Reflection;
using ConfigureEFDbContext.EFProviderConnectionOptions;
using ConfigureEFDbContext.Options;
using Microsoft.Extensions.Options;namespace ConfigureEFDbContext
{public interface IDbContextConfigurerFactory{IDbContextConfigurer GetConfigurer(string migrationsAssembly = null);}public class DbContextConfigurerFactory : IDbContextConfigurerFactory{public DbContextConfigurerFactory(IOptions<EntityFrameworkOptions> options,IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions){EntityFrameworkOptions = options.Value;Factories = new Dictionary<string, Func<string, IDbContextConfigurer>>() {{EntityFrameworkProviders.SqlServer, (migrationsAssembly) =>CreateSqlServerSetup(dbProviderConnectionOptions, migrationsAssembly)},{EntityFrameworkProviders.SQLite, (migrationsAssembly) =>CreateSqliteSetup(dbProviderConnectionOptions, migrationsAssembly)},{EntityFrameworkProviders.InMemor, (migrationsAssembly) =>CreateInMemorySetup(dbProviderConnectionOptions, migrationsAssembly)},};}protected EntityFrameworkOptions EntityFrameworkOptions { get; }protected Dictionary<string, Func<string, IDbContextConfigurer>> Factories { get; }public virtual IDbContextConfigurer GetConfigurer(string migrationsAssembly = null)=> Factories.ContainsKey(EntityFrameworkOptions.Provider)? Factories[EntityFrameworkOptions.Provider](migrationsAssembly ?? Assembly.GetCallingAssembly().GetName().Name): default;protected virtual IDbContextConfigurer CreateSqlServerConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) => new SqlServerDbContextConfigurer(dbProviderConnectionOptions, migrationsAssembly);protected virtual IDbContextConfigurer CreateSqliteConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) => new SqliteDbContextConfigurer(dbProviderConnectionOptions, migrationsAssembly);protected virtual IDbContextConfigurer CreateInMemoryConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) => new InMemoryDbContextConfigurer(dbProviderConnectionOptions, migrationsAssembly);}public class CacheableDbContextConfigurerFactory : DbContextConfigurerFactory{protected IDbContextConfigurer _sqlServerConfigurer;protected IDbContextConfigurer _sqliteConfigurer;protected IDbContextConfigurer _inMemoryConfigurer;public CacheableDbContextConfigurerFactory(IOptions<EntityFrameworkOptions> options,IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions) :base(options, dbProviderConnectionOptions) { }protected override IDbContextConfigurer CreateSqlServerConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) => _sqlServerConfigurer ??= base.CreateSqlServerSetup(dbProviderConnectionOptions, migrationsAssembly);protected override IDbContextConfigurer CreateSqliteConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) => _sqliteConfigurer ??= base.CreateSqliteSetup(dbProviderConnectionOptions, migrationsAssembly);protected override IDbContextConfigurer CreateInMemoryConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) => _inMemoryConfigurer ??= base.CreateInMemorySetup(dbProviderConnectionOptions, migrationsAssembly);}
}
这是Product接口和具体类的实现:
using System;
using ConfigureEFDbContext.EFProviderConnectionOptions;
using Microsoft.EntityFrameworkCore;namespace ConfigureEFDbContext
{public interface IDbContextConfigurer{void Configure(IServiceProvider serviceProvider, DbContextOptionsBuilder builder);}public abstract class DbContextConfigurer : IDbContextConfigurer{protected DbContextConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly){DbProviderConnectionOptions = dbProviderConnectionOptions;MigrationsAssembly = migrationsAssembly;}public IEntityFrameworkProviderConnectionOptions DbProviderConnectionOptions { get; }public string MigrationsAssembly { get; }public abstract void Configure(IServiceProvider serviceProvider,DbContextOptionsBuilder builder);}public class SqlServerDbContextConfigurer : DbContextConfigurer{public SqlServerDbContextConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) : base(dbProviderConnectionOptions, migrationsAssembly) { }public override void Configure(IServiceProvider serviceProvider,DbContextOptionsBuilder builder){if (DbProviderConnectionOptions.UseConnectionString){builder.UseSqlServer(connectionString: DbProviderConnectionOptions.GetConnectionString(),sqlServerDbContextOptionsBuilder =>sqlServerDbContextOptionsBuilder.MigrationsAssembly(MigrationsAssembly));}else{builder.UseSqlServer(connection: DbProviderConnectionOptions.GetConnection(),sqlServerDbContextOptionsBuilder =>sqlServerDbContextOptionsBuilder.MigrationsAssembly(MigrationsAssembly));}}}public class SqliteDbContextConfigurer : DbContextConfigurer{public SqliteDbContextConfigurer(IEntityFrameworkProviderConnectionOptions dbProviderConnectionOptions,string migrationsAssembly) : base(dbProviderConnectionOptions, migrationsAssembly) { }public override void Configure(IServiceProvider serviceProvider,DbContextOptionsBuilder builder){if (DbProviderConnectionOptions.UseConnectionString){builder.UseSqlite(connectionString: DbProviderConnectionOptions.GetConnectionString(),sqlServerDbContextOptionsBuilder =>sqlServerDbContextOptionsBuilder.MigrationsAssembly(MigrationsAssembly));}else{builder.UseSqlite(connection: DbProviderConnectionOptions.GetConnection(),sqlServerDbContextOptionsBuilder =>sqlServerDbContextOptionsBuilder.MigrationsAssembly(MigrationsAssembly));}}}public class InMemoryDbContextConfigurer : DbContextConfigurer{public InMemoryDbContextConfigurer(IEntityFrameworkProviderConnectionOptionsdbProviderConnectionOptions, string migrationsAssembly) :base(dbProviderConnectionOptions, migrationsAssembly) { }public override void Configure(IServiceProvider serviceProvider,DbContextOptionsBuilder builder) => builder.UseInMemoryDatabase(DbProviderConnectionOptions.GetConnectionString());}
}
这个工厂负责通过调用GetConfigurer方法创建将配置DbContext的类,我们将获得一个包含该Configure 方法的IDbContextConfigurer实例来初始化DbContext。
实体框架提供者连接选项
为了使Configure 方法更加灵活,我们遵循了单一责任原则(SRP)。因此,我们创建了一些新类。
这个简单设计的主要任务是通过前面讨论的选项模式从appsetting.json或当前环境设置文件中读取配置,并将其转换为可应用于数据提供程序的扩展名,因此如果要添加新的提供程序,我们必须为此提供程序添加另一个类,但不要忘记向IDbContextConfigurer接口添加新的实现或从DbContextConfigurer基类继承,例如: MySqlProviderConnectionOptions
using System.Data.Common;
using ConfigureEFDbContext.Common;
using ConfigureEFDbContext.Options;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Options;namespace ConfigureEFDbContext
{public interface IEntityFrameworkProviderConnectionOptions : IDisposableObject{bool UseConnectionString { get; }string GetConnectionString();DbConnection GetConnection();}public abstract class EntityFrameworkProviderConnectionOptions : DisposableObject,IEntityFrameworkProviderConnectionOptions{public abstract bool UseConnectionString { get; }public virtual DbConnection GetConnection() => null;public virtual string GetConnectionString() => null;}public class SqlServerProviderConnectionOptions : EntityFrameworkProviderConnectionOptions{private readonly ConnectionStringsOptions _options;public SqlServerProviderConnectionOptions(IOptions<ConnectionStringsOptions> options) => _options = options.Value;public override bool UseConnectionString => true;public override string GetConnectionString() =>$"Server={_options.ServerName};Database={_options.DatabaseName};User Id={_options.UserId};Password={_options.Password};MultipleActiveResultSets=True";// Or//public string GetConnectionString() {// var connectionStringBuilder = new SqlConnectionStringBuilder();// connectionStringBuilder.DataSource = _options.ServerName;// connectionStringBuilder.DataSource = _options.DatabaseName;// connectionStringBuilder.UserID = _options.UserId;// connectionStringBuilder.Password = _options.Password;// connectionStringBuilder.MultipleActiveResultSets = true;// return connectionStringBuilder.ConnectionString;//}}public class SqliteProviderConnectionOptions : EntityFrameworkProviderConnectionOptions{private readonly ConnectionStringsOptions _options;public SqliteProviderConnectionOptions(IOptions<ConnectionStringsOptions> options) => _options = options.Value;public override bool UseConnectionString => true;//If _options.InMemory then Data Source=InMemorySample;Mode=Memory;Cache=Sharedpublic override string GetConnectionString() =>$"Data Source={_options.DatabaseName};Cache=Shared;";}public class SqliteInMemoryProviderConnectionOptions :EntityFrameworkProviderConnectionOptions{private readonly DbConnection _connection;public SqliteInMemoryProviderConnectionOptions() =>_connection = new SqliteConnection("Data Source=:memory:;Cache=Shared;");public override bool UseConnectionString => false;public override DbConnection GetConnection(){if (_connection.State != System.Data.ConnectionState.Open){_connection.Open();}return _connection;}protected override void Dispose(bool disposing){_connection.Dispose();base.Dispose(disposing);}}public class InMemoryProviderConnectionOptions : EntityFrameworkProviderConnectionOptions{private readonly ConnectionStringsOptions _options;public InMemoryProviderConnectionOptions(IOptions<ConnectionStringsOptions> options) => _options = options.Value;public override bool UseConnectionString => true;public override string GetConnectionString() => _options.DatabaseName;}
}
完成此方案后,我们必须在Startup类中注入“依赖注入”中的所有类。您会在这里注意到,我们添加到Startup类中的所有新方法都是virtual方法。这是为了使此类在MSTest单元测试的集成测试应用程序中可重写,我们将在以后添加。
using ConfigureEFDbContext.Options;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;namespace ConfigureEFDbContext
{public class Startup{public Startup(IConfiguration configuration, IWebHostEnvironment hostEnvironment){Configuration = configuration;HostEnvironment = hostEnvironment;}public IConfiguration Configuration { get; }public IWebHostEnvironment HostEnvironment { get; }// This method gets called by the runtime. Use this method to add services// to the container.public virtual void ConfigureServices(IServiceCollection services){this.AddLogging(services).AddApplicationOptions(services).AddDbContextConfigurerFactory(services).AddEFProviderConnectionOptions(services).AddDbContextConfigurer(services).AddDbContext(services);services.AddControllers();}// This method gets called by the runtime. Use this method to configure// the HTTP request pipeline.public virtual void Configure(IApplicationBuilder app){if (HostEnvironment.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseHttpsRedirection().UseRouting().UseAuthorization().UseEndpoints(endpoints => endpoints.MapControllers());}// To use fluent setting we return same objectsprotected virtual Startup AddLogging(IServiceCollection services){services.AddLogging(builder =>builder.AddConfiguration(Configuration.GetSection("Logging")).AddConsole().AddDebug());return this;}// To use fluent setting we return same objectsprotected virtual Startup AddApplicationOptions(IServiceCollection services){services.AddOptions().Configure<EntityFrameworkOptions>(Configuration.GetSection(EntityFrameworkOptions.KEY_NAME)).Configure<ConnectionStringsOptions>(Configuration.GetSection(ConnectionStringsOptions.KEY_NAME));return this;}protected virtual Startup AddDbContextConfigurerFactory(IServiceCollection services){services.AddSingleton<IDbContextConfigurerFactory,CacheableDbContextConfigurerFactory>();return this;}protected virtual Startup AddEFProviderConnectionOptions(IServiceCollection services){services.AddSingleton<IEntityFrameworkProviderConnectionOptions,SqlServerProviderConnectionOptions>();return this;}protected Startup AddDbContextConfigurer(IServiceCollection services){services.AddSingleton(serviceProvider =>serviceProvider.GetService<IDbContextConfigurerFactory>().GetConfigurer());return this;}protected virtual Startup AddDbContext(IServiceCollection services){AddDbContextPool<DbContext_1>(services);AddDbContextPool<DbContext_2>(services);AddDbContextPool<DbContext_3>(services);// Interface Segregation Principle (ISP)services.AddScoped<IDbContext_1>(provider => provider.GetService<DbContext_1>());services.AddScoped<IDbContext_2>(provider => provider.GetService<DbContext_2>());services.AddScoped<IDbContext_3>(provider => provider.GetService<DbContext_3>());return this;}private Startup AddDbContextPool<TContext>(IServiceCollection services)where TContext : DbContext{services.AddDbContextPool<TContext>((serviceProvider, dbContextOptionsBuilder) =>serviceProvider.GetService<IDbContextConfigurer>().Configure(serviceProvider, dbContextOptionsBuilder));return this;}}
}
最后,我们将添加我们在讨论中使用的DbContext,这是这种类型的类的最简单的标识形式。
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;namespace ConfigureEFDbContext
{// Interface Segregation Principle (ISP) or Encapsulationpublic interface IDbContext{/// <summary>/// Provides access to database related information and operations for this context./// </summary>DatabaseFacade Database { get; }}public interface IDbContext_1 : IDbContext { }public interface IDbContext_2 : IDbContext { }public interface IDbContext_3 : IDbContext { }public class DbContext_1 : DbContext, IDbContext_1{public DbContext_1(DbContextOptions<DbContext_1> options) : base(options) { }}public class DbContext_2 : DbContext, IDbContext_2{public DbContext_2(DbContextOptions<DbContext_2> options) : base(options) { }}public class DbContext_3 : DbContext, IDbContext_3{public DbContext_3(DbContextOptions<DbContext_3> options) : base(options) { }}
}
在这里,我们将添加一个控制器,以便我们对该场景进行简单测试。
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;namespace ConfigureEFDbContext.Controllers
{[ApiController][Route("[controller]")]public class DbContextsController : ControllerBase{private readonly IDbContext_1 _dbContext_1;private readonly IDbContext_2 _dbContext_2;private readonly IDbContext_3 _dbContext_3;public DbContextsController(IDbContext_1 dbContext_1,IDbContext_2 dbContext_2, IDbContext_3 dbContext_3){_dbContext_1 = dbContext_1;_dbContext_2 = dbContext_2;_dbContext_3 = dbContext_3;}[HttpGet]public IEnumerable<string> Get() => new[] {_dbContext_1.Database.ProviderName,_dbContext_2.Database.ProviderName,_dbContext_3.Database.ProviderName};}
}
用Postman测试程序后,我们将得到以下结果:
在本文中,我们将不讨论单元测试或集成测试,因为它不在本主题的讨论范围内,但我们将展示运行该测试所需的类。
{"EntityFrameworkOptions": {"Provider": "IN-MEMOR","CanMigrate": false//"Provider": "SQLITE",//"CanMigrate": true},"ConnectionStringsOptions": {"ServerName": null,"DatabaseName": "DEV-V1.db","UserId": null,"Password": null}
}
集成测试
从我们之前创建的Startup类继承并覆盖必要的方法后,IntegrationStartup 将变为:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;namespace ConfigureEFDbContext.MSUnitTest
{public class IntegrationStartup : Startup{public override void ConfigureServices(IServiceCollection services){base.ConfigureServices(services);services.AddMvc().AddApplicationPart(typeof(Startup).Assembly);}public IntegrationStartup(IConfiguration configuration,IWebHostEnvironment environment) : base(configuration, environment) { }/** ************************************************************************************* We have to choose the correct Provider according to the settings that we put in* the configuration 'integration-settings.json' file, Section 'EntityFrameworkOptions'* *************************************************************************************/// FOR EntityFrameworkProviders.SQLite//protected override Startup AddEFProviderConnectionOptions// (IServiceCollection services)//{// services.AddSingleton<IEntityFrameworkProviderConnectionOptions,// SqliteProviderConnectionOptions>();// return this;//}// FOR EntityFrameworkProviders.SQLite but in memory db.//protected override // Startup AddEFProviderConnectionOptions(IServiceCollection services)//{// services.AddSingleton<IEntityFrameworkProviderConnectionOptions,// SqliteInMemoryProviderConnectionOptions>();// return this;//}// FOR EntityFrameworkProviders.InMemorprotected override Startup AddEFProviderConnectionOptions(IServiceCollection services){services.AddSingleton<IEntityFrameworkProviderConnectionOptions,InMemoryProviderConnectionOptions>();return this;}}
}
运行此测试所需的类。
using System;
using System.IO;
using System.Reflection;namespace ConfigureEFDbContext.MSUnitTest
{public static class Helper{public static string GetParentProjectPath(){var parentProjectName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;var parentProjectFullName = $"{parentProjectName}.csproj";var applicationBasePath = Directory.GetCurrentDirectory();var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory());while (directoryInfo != null){var projectDirectoryInfo = new DirectoryInfo(directoryInfo.FullName);var parentProjectPath = Path.Combine(projectDirectoryInfo.FullName,parentProjectName, parentProjectFullName);if (projectDirectoryInfo.Exists && new FileInfo(parentProjectPath).Exists){return Path.Combine(projectDirectoryInfo.FullName, parentProjectName);}directoryInfo = directoryInfo.Parent;}throw new Exception($"Th parent project {parentProjectName}could not be located using the current application root {applicationBasePath}.");}}
}
通过从WebApplicationFactory继承创建独立于测试类的IntegrationWebApplicationFactory类。
using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;namespace ConfigureEFDbContext.MSUnitTest.Fixtures
{public class IntegrationWebApplicationFactory : WebApplicationFactory<Startup>{protected override IWebHostBuilder CreateWebHostBuilder() =>WebHost.CreateDefaultBuilder<IntegrationStartup>(null);protected override void ConfigureWebHost(IWebHostBuilder builder){var contentRoot = Helper.GetParentProjectPath();builder.ConfigureAppConfiguration(config =>{var projectDir = Directory.GetCurrentDirectory();var integrationSettingsPath =Path.Combine(projectDir, "integration-settings.json");var integrationConfig = new ConfigurationBuilder().SetBasePath(contentRoot).AddJsonFile(integrationSettingsPath, false).Build();config.AddConfiguration(integrationConfig);}).UseContentRoot(contentRoot).UseEnvironment("Development").UseStartup<IntegrationStartup>();// Here we can also write our own settings// this called after the 'ConfigureServices' from the Startup// But this is not desirable because we will hide the dependencies// and break the Single Responsibility Principle (SRP).// builder.ConfigureServices(services => {});// Orbuilder.ConfigureTestServices(services =>{//services// .AddMvc()// .AddApplicationPart(typeof(Startup).Assembly);});base.ConfigureWebHost(builder);}}
}
一个类来测试我们的控制器类,以确保一切正常。
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using ConfigureEFDbContext.MSUnitTest.Fixtures;
using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;namespace ConfigureEFDbContext.MSUnitTest
{[TestClass]public class DbContextsControllerTest{protected static IntegrationWebApplicationFactory _fixture;protected static HttpClient _client;protected readonly IConfiguration _configuration;/// <summary>/// Executes once for the test class. (Optional)/// </summary>[ClassInitialize]public static void TestFixtureSetup(TestContext context){_fixture = new IntegrationWebApplicationFactory();_client = _fixture.CreateClient();_client.BaseAddress = new Uri("http://localhost:60128");_client.DefaultRequestHeaders.Accept.Clear();_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));}/// <summary>/// Runs before each test. (Optional)/// </summary>[TestInitialize]public void Setup() { }/// <summary>/// Runs once after all tests in this class are executed. (Optional)/// Not guaranteed that it executes instantly after all tests from the class./// </summary>[ClassCleanup]public static void TestFixtureTearDown(){_client.Dispose();_fixture.Dispose();}/// <summary>/// Runs after each test. (Optional)/// </summary>[TestCleanup]public void TearDown() { }[TestMethod]public async Task DbContexts__Should_Initialized(){/** ===== ===== ===== ===== ===== ===== ===== ===== ===== =====* Arrange* this is where you would typically prepare everything for the test,* in other words, prepare the scene for testing* (creating the objects and setting them up as necessary)* ===== ===== ===== ===== ===== ===== ===== ===== ===== =====*/var requestUri = new Uri("/DbContexts", UriKind.Relative);/** ===== ===== ===== ===== ===== ===== ===== ===== ===== =====* Act* this is where the method we are testing is executed.* ===== ===== ===== ===== ===== ===== ===== ===== ===== =====*/var response = await _client.GetAsync(requestUri).ConfigureAwait(false);var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);response.EnsureSuccessStatusCode();var result = JsonConvert.DeserializeObject<List<string>>(responseBody);/** ===== ===== ===== ===== ===== ===== ===== ===== ===== =====* Assert* This is the final part of the test where we compare what we expect* to happen with the actual result of the test method execution.* ===== ===== ===== ===== ===== ===== ===== ===== ===== =====*/Assert.IsNotNull(result);}}
}
结论
在本文中,我展示了一种使用灵活的startup方法来实现此配置的新方法,该方法可通过类配置应用程序。
您可以在GitHub上找到此演示的源代码。
配置实体框架DbContext的可扩展方案相关推荐
- ADO.NET Entity Framework如何:手动配置实体框架项目
如果在 Visual Studio 项目中使用实体数据模型向导,该向导将自动生成 .edmx 文件并将该项目配置为使用实体框架.有关更多信息,请参见 如何:使用实体数据模型向导(实体框架). 也可以手 ...
- ADO.NET Entity Framework如何:通过每种类型一个表继承以定义模型(实体框架)
本主题介绍如何手动创建具有每种类型一个表继承层次结构的概念模型.每种类型一个表继承使用数据库中单独的表为继承层次结构中的每种类型维护非继承属性和键属性的数据. 说明: 建议使用 ADO.NET 实体数 ...
- ADO.NET Entity Framework建模和映射(实体框架)
在实体框架中,可以采用最适合您应用程序的方式定义概念模型.存储模型以及这两种模型之间的映射.使用 Visual Studio 中的实体数据模型工具,可以从数据库或图形模型创建一个 . edmx 文件, ...
- ADO.NET Entity Framework如何:使用实体数据模型向导(实体框架)
本主题演示如何使用实体数据模型向导来生成 AdventureWorks 销售 .edmx 文件以及将 Visual Studio 项目配置为使用实体框架. 此模型和配置将在任务相关的各个实体框架主题中 ...
- Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)
在前面几篇关于Entity Framework 实体框架的介绍里面,已经逐步对整个框架进行了一步步的演化,以期达到统一.高效.可重用性等目的,本文继续探讨基于泛型的仓储模式实体框架方面的改进优化,使我 ...
- 实体框架高级应用之动态过滤 EntityFramework DynamicFilters
实体框架高级应用之动态过滤 EntityFramework DynamicFilters 实体框架高级应用之动态过滤 EntityFramework DynamicFilters 我们开门见山,直奔主 ...
- Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)
很久没有写博客了,一些读者也经常问问一些问题,不过最近我确实也很忙,除了处理日常工作外,平常主要的时间也花在了继续研究微软的实体框架(EntityFramework)方面了.这个实体框架加入了很多特性 ...
- 使用实体框架核心创建简单的审计跟踪
目录 介绍 背景 审核日志数据 审核表实体 审核类型 审计Db上下文 审核表配置 数据更改到审核表 实体更改为审核表实体 所有实体更改为审计表 在现有的DbContext中使用审核跟踪 创建DbCon ...
- 使用实体框架返回数据表
目录 背景 扩展方式 实体框架(Entity Framework) 实体框架核心(Entity Framework Core) 使用扩展方法 使用常规查询 使用参数化查询 不同数据库的DbParame ...
最新文章
- 使用C#.NET列举组合数前N项和
- ActiveMQ目录结构
- 【Linux】一步一步学Linux——w命令(227)
- vue 如何处理两个组件异步问题_Vue异步组件使用详解
- 简洁的c++http协议获取内容(一)
- 利用 Conda 尝鲜 Python 3.10 不一样的特性 快来试试
- python中的stopwords_中文分词停止词stopwords词典,可下载
- 云优CMS发布接口模块—免登录通用版
- CentOS7.6的详细安装步骤
- ImageNet中英文类别对照
- 阿里云视频点播Demo
- 自由人NFT元农(Meta Agriculture)发行计划
- 19考研报名系统今日关闭!记得检查!研招现场确认最全提醒
- Myeclipes解决SECURITY ALTER:INTEGRITY CHECK ERROR
- Typora自定义主题#简约风主题
- 可可西里观后感(转)-保护藏羚羊
- 报错:ERROR: for nginx Cannot start service proxy;for proxy Cannot start service proxy;......
- Matlab --- Matlab中常数c乘以矩阵A背后的故事
- 文科生读计算机博士,文科类哪些专业博士前景好?看完这篇就懂了!
- MATLAB十进制转其它进制的代码
热门文章
- python数据包头_Python爬虫-请求响应包头
- css按钮大小固定,在CSS中创建一个固定宽度的按钮
- java纯数字正则表达式_JAVA验证数字的正则表达式,来一发
- 爬虫获取不到网页完整源码_你的第一只网络爬虫
- 传统新年元旦海报设计,必备高品质吉祥图案背景
- 促销海报创意|你想要的秋天(秋季),吸睛大促海报都在这里
- APP界面设计的视觉思维!
- 优秀PSD电商促销BANNER模板|垂直化内容电商页面设计,需要注意哪些问题?
- 图标代码_通过两行代码即可调整苹果电脑 Launchpad 图标大小!
- 干支纪年法简便算法_@谢氏宗亲:可知道我国为何放弃黄帝纪年,而选择耶稣诞辰纪年法...