当调用新的 DbContext 时,DbContextOptions 中包含哪些内容?

我没有使用 DI,只是想在控制器中调用 DbContext。我正在苦苦思索“选项”应该是什么?

Applicationdbcontext.cs

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{


public DbSet<Gig> Gigs { get; set; }
public DbSet<Genre> Genres { get; set; }




public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}


protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}

Gigscontroller.cs

    public class GigsController : Controller
{
private ApplicationDbContext _context;


public GigsController()
{
_context = new ApplicationDbContext();
}




public IActionResult Create()
{
var viewModel = new GigFormViewModel
{
Genres = _context.Genres.ToList()
};




return View(viewModel);
}
}

这个问题源于我的 GigsController 构造函数:

_context = new ApplicationDbContext();

我出错了,因为我需要向 ApplicationDbContext 传递一些内容。没有给出与“ ApplicationDbContext”所需的形式参数“ options”相对应的参数。ApplicationDbContext (DbContextOptions)’

我尝试在衍生自 base ()的 ApplicationdbContext 中创建一个缺省构造函数,但也没有用。

在我的 startup.cs 中,我已经配置了 ApplicationDbContext

        public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));


services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();


services.AddMvc();


// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
94928 次浏览

如果您想手动创建上下文,那么您可以 配置它像这样:

var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer(Configuration.GetConnectionStringSecureValue("DefaultConnection"));
_context = new ApplicationDbContext(optionsBuilder.Options);

(DbContextOptionsBuilder<ApplicationDbContext>类是 services.AddDbContext<ApplicationDbContext>(options =>options参数的类型)。 但是在控制器中,您不能访问 Configuration对象,所以您必须将它作为 Startup.cs中的静态字段公开,或者使用其他一些技巧,这些都是不好的做法。

获得 ApplicationDbContext的最佳方法是通过 DI:

public GigsController(ApplicationDbContext context)
{
_context = context;
}

DI 容器将负责实例化 ApplicationDbContext和处理。请注意,您在 Startup.cs中已经正确配置了一切:

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));


services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

那就是配置 DI,为什么不直接使用它呢?

关于 DbContext的缺省构造函数,还有一点需要注意: 在 EF6中是这样做的: public ApplicationDbContext(): base("DefaultConnection") {}。然后,基对象将使用 System.Configuration.ConfigurationManager静态类从 web.config获取名为 DefaultConnection的连接字符串。新的 Asp.net Core 和 EF Core 被设计成尽可能地解耦,所以它不应该依赖于任何配置系统。相反,您只需要传递一个 DbContextOptions对象——创建该对象并配置它是一个单独的问题。

我是这么做的:

public class GigsController : Controller
{
private readonly IConfiguration _configuration;
private string _connectionString;
DbContextOptionsBuilder<ApplicationDbContext> _optionsBuilder;


public GigsController (IConfiguration configuration)
{
_configuration = configuration;
_optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
_connectionString = _configuration.GetConnectionString("DefaultConnection");
_optionsBuilder.UseSqlServer(_connectionString);
}


public IActionResult Index()
{
using(ApplicationDbContext _context = new ApplicationDbContext(_optionsBuilder.Options))
{
// .....Do something here
}
}
}

最近,我正在将一个非常大的数据集迁移到数据库中(大约1000万个) ,一个上下文实例将很快耗尽我所有的内存。因此,我必须创建一个新的 Context 实例,并在某个阈值之后释放旧的实例以释放内存。

这不是一个优雅的解决方案,但对我有效。

我个人不明白为什么你不想使用 DI,只是通过在控制器的构造函数中指定 (ApplicationDbContext db)来代表你创建 DI,事实上你正在用你的代码注册 DI:

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

如果您绝对想要显式地调用 new ApplicationDbContext(...),请记住,在控制器中调用 Configuration 不是一个好主意,但是您需要配置来获得连接细节,因为您必须提供 DbContextOptions 作为上下文的参数。我建议完全移除 services.AddDbContext,因为我从未打算解决它。内置的 DI 似乎没有一个干净的方法来进行“工厂”注册。我使用 Autofac 和 Simple Injector,它们在 lambda 表达式中提供了非常简洁的方法,可以按照以下方式进行:

containerBuilder.Register(c =>
{
var optionsBuilder = new DbContextOptionsBuilder<EntityDbContext>()
.UseSqlServer(Configuration.GetConnectionStringSecureValue("DefaultConnection"));


return optionsBuilder.Options;
});

然后你可以简单地这样做:

public GigsController(DbContextOptionsBuilder<EntityDbContext> dbContextOptions)
{
_context = new ApplicationDbContext(dbContextOptions);
}

因此,如果你要整合自动工厂,这是一种方法。

我只是设法让我的头周围所有这些注入的东西和配置,并有一个很好的干净的解决方案,这将解决你的问题,包括读配置。其思想是从 appsetings.json 中读取配置,将其分配给配置类上的连接字符串属性:

public interface IDatabaseConfig
{
string ConnectionString { get; set; }
}


public class DatabaseConfig : IDatabaseConfig
{
public DatabaseConfig()
{
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();


ConnectionString = configuration.GetSection("Database").GetValue<string>("ConnectionString");
}


public string ConnectionString { get; set; }


}

然后在 ConfigureServices 中注册接口:

services.AddTransient<IDatabaseConfig, DatabaseConfig>();

并使用接口作为控制器构造函数参数,然后您可以创建选项:

public GigsController(IDatabaseConfig dbConfig)
{
var dbContextOptions = new DbContextOptions<ApplicationDbContext>().UseSqlServer(dbConfig.ConnectionString);
_context = new ApplicationDbContext(dbContextOptions);
}

我不喜欢直接在这个类上创建配置构建器。NET Core 已经提供了一个,所有这些都应该在 Startup 类中完成。最佳实践是使用以下方法从应用程序设置反序列化 DatabaseConfig:

var databaseConfig = configuration.GetSection("Database").Get<DatabaseConfig>();

但是因为我找不到一种方法来注册这个实例或者将它推迟到 DI 工厂样式的注册,所以这不是一个选项。

实际上,最好像最初那样使用 serices.AddDbContext<ApplicationDbContext>(...),并将它作为构造函数参数注入到控制器中,这样问题就解决了。

我个人处理这整个场景的方法是,根据自己的喜好自由配置选项,甚至切换 SqlServer 连接到内存中的 db,以便运行集成测试,而在 CI 构建中,您无法访问真正的 db,如下所示。

我有一个 DatabaseConfig 对象图:

public class Config
{
public DatabaseConfig Database { get; set; }


}


public interface IDatabaseConfig
{
InMemoryConfig InMemory { get; set; }
string ConnectionString { get; set; }
}


public class DatabaseConfig : IDatabaseConfig
{
public InMemoryConfig InMemory { get; set; }
public string ConnectionString { get; set; }


}


public class InMemoryConfig
{
public bool Enabled { get; set; }
public string Name { get; set; }


}

它与这个结构保持一致,并从 appsetings.json 反序列化:

"Database": {
"InMemory": {
"Enabled": true,
"Name": "Snoogans"
},
"ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=SomeDb;Trusted_Connection=True;"
}

一个开箱即用的选项就是这样做

var databaseConfig = configuration.GetSection("Database").Get<DatabaseConfig>();

但是我使用 Autofac,有一个叫做 分散,配置,自动工厂的软件包,它允许你用 ConfigureContainer方法做这件事:

builder.RegisterModule<ConfigurationModule<JsonResolver<Config>>>();

如果 Config 图上的属性实现了一个接口,则使用 Autofac 进行注册,并将设置反序列化到服务实例上。这本身就足以在任何控制器上注入 IDatabaseConfig 作为构造函数参数,然后您可以自己创建它,但是最好在一个地方完成,否则您必须在使用它的任何地方不断重复 DbContextOptionsBuilder 逻辑。

因此,我使用工厂注册来跟踪 ConfigurationModule 注册,它使用 appsetings.json 中的选项创建我的 db 上下文:

containerBuilder.Register(c =>
{
var optionsBuilder = new DbContextOptionsBuilder<EntityDbContext>();
optionsBuilder = databaseConfig.InMemory.Enabled
? optionsBuilder.UseInMemoryDatabase(databaseConfig.InMemory.Name)
: optionsBuilder.UseSqlServer(databaseConfig.ConnectionString);


return optionsBuilder.Options;
});

这是一个干净的解决方案,责任不会泄露到他们不应该泄露的领域。您的控制器不应该负责数据库 ORM 的创建。它应该只是给一个预先创建使用,否则太难以改变以后。考虑一下,如果您有500个控制器,您可以在所有情况下手动创建它们,而不是传递一个预先创建的实例,其中创建代码在一个地方完成。更进一步,我的 db 上下文实现了 IWriteEntities 和 IReadEntities,那么它就更抽象了,你可以切换到另一个完整的 DbContext 子类,你可以简化重做到一行,在这里注册 db 上下文。