调用新的DbContext时DbContextOptions会发生什么?

时间:2016-07-17 01:11:28

标签: entity-framework asp.net-core entity-framework-core

我没有使用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。没有任何论据符合所要求的形式参数&#39;选项&#39; &#39; ApplicationDbContext.ApplicationDbContext(DbContextOptions)&#39;

我尝试在从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>();
    }

4 个答案:

答案 0 :(得分:39)

如果您真的想要手动创建上下文,那么您可以configure这样:

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

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

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

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

DI容器将负责实例化{strong>并处理 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静态类从DefaultConnection获取名为web.config的连接字符串。新的Asp.net Core和EF Core旨在尽可能多地解耦,因此它不应该依赖于任何配置系统。相反,您只需传递一个DbContextOptions对象 - 创建该对象并对其进行配置是一个单独的问题。

答案 1 :(得分:1)

这就是我要做的:

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实例,并在一定阈值后处置旧的Context实例以释放内存。

这不是一个很好的解决方案,但对我有用。

答案 2 :(得分:1)

我个人不明白为什么您不想使用DI,而只是通过在控制器的构造函数中指定(ApplicationDbContext db)来代表您创建它,实际上您是使用代码在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);
}

因此,如果要集成Autofac,那是一种方法。

我刚刚设法弄清楚了所有这些注射剂和配置,并有了一个不错的干净解决方案,它将解决您的问题,包括读取配置。想法是您从appsettings.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(dbContextOptions.ConnectionString);
    _context = new ApplicationDbContext(dbContextOptions);
}

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

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

但是我看不到一种方法来注册该实例或将其推迟到DI工厂样式注册,所以这不是一个选择。

真的,您最好像原来一样使用serices.AddDbContext<ApplicationDbContext>(...)并将其作为构造函数参数注入控制器中,这样就可以解决问题。

我个人解决整个情况的方式使您可以自由配置所需的选项,甚至可以将SqlServer连接切换为内存中的数据库,以运行集成测试,而您无法访问实际的数据库。 CI构建的一部分,如下所示...

我有一个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; }

}

与此结构保持一致,并从appsettings.json中反序列化:

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

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

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

但是我使用的是Autofac,还有一个名为Divergic.Configuration.Autofac的可爱的nuget包,它允许您使用ConfigureContainer方法执行此操作:

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

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

因此,我在ConfigurationModule注册后进行了工厂注册,该注册使用appsettings.json中的选项创建了我的数据库上下文:

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个控制器在所有情况下都可以手动创建它们,而不是传递一个在一个地方完成创建代码的预先创建的实例。如果在我的数据库上下文实现IWriteEntities和IReadEntities时再进一步,那么它就更加抽象了,您可以切换到另一个完整的DbContext子类,并将重做简化为注册数据库上下文的一行。

答案 3 :(得分:0)

 public class PaymentDetailContext:DbContext //inherit this class from DB context from entity framework core DB Context 
    {
        public PaymentDetailContext(DbContextOptions<PaymentDetailContext> options):base(options) 
            //CONSTRUCTOR-------------PARAMETER----------TYPE----------------NAME---PARENT CONTSTRUCTOR
        {

        }
        public DbSet<PaymentDetail> PaymentDetails { get; set; }
    }