Использовать DbContext в ASP.Net Singleton Injected Class
Мне нужно получить доступ к моей базе данных в классе Singleton, созданном в моем классе Startup. Кажется, что инъекция непосредственно приводит к размещению DbContext.
Я получаю следующую ошибку:
Невозможно получить доступ к удаленному объекту. Имя объекта: "MyDbContext".
Мой вопрос двоякий: почему это не работает и как я могу получить доступ к моей базе данных в экземпляре класса singleton?
Вот мой метод ConfigureServices в моем классе Startup:
public void ConfigureServices(IServiceCollection services)
{
// code removed for brevity
services.AddEntityFramework().AddSqlServer().AddDbContext<MyDbContext>(
options =>
{
var config = Configuration["Data:DefaultConnection:ConnectionString"];
options.UseSqlServer(config);
});
// code removed for brevity
services.AddSingleton<FunClass>();
}
Вот мой класс контроллера:
public class TestController : Controller
{
private FunClass _fun;
public TestController(FunClass fun)
{
_fun = fun;
}
public List<string> Index()
{
return _fun.GetUsers();
}
}
Вот мой FunClass:
public class FunClass
{
private MyDbContext db;
public FunClass(MyDbContext ctx) {
db = ctx;
}
public List<string> GetUsers()
{
var lst = db.Users.Select(c=>c.UserName).ToList();
return lst;
}
}
Ответы
Ответ 1
Причина, по которой он не работает, заключается в том, что расширение .AddDbContext
добавляет его как область действия для каждого запроса. Скопированный на запрос, как правило, то, что вы хотите, и обычно сохранение изменений будет вызываться один раз за запрос, а затем dbcontext
будет размещен в конце запроса.
Если вам действительно нужно использовать dbcontext
внутри singleton
, то ваш класс FunClass
должен, вероятно, зависеть от IServiceProvider
и DbContextOptions
вместо того, чтобы напрямую зависеть от dbcontext
, таким образом вы можете создать его самостоятельно.
public class FunClass
{
private GMBaseContext db;
public FunClass(IServiceProvider services, DbContextOptions dbOptions)
{
db = new GMBaseContext(services, dbOptions);
}
public List<string> GetUsers()
{
var lst = db.Users.Select(c=>c.UserName).ToList();
return lst;
}
}
Тем не менее, мой совет будет состоять в том, чтобы тщательно рассмотреть вопрос о том, действительно ли вам нужен ваш FunClass для сингла, я бы избегал этого, если у вас нет веских оснований для того, чтобы сделать его одиночным.
Ответ 2
Обновить
Я полностью осознаю, что это решение не является правильным способом сделать это. Пожалуйста, не делай то, что я делал здесь все эти годы назад. На самом деле, не вводите синглтон DbContext вообще.
Старый ответ
Решением было вызвать AddSingleton с экземпляром моего класса в параметре метода в моем классе запуска:
services.AddSingleton(s => new FunClass(new MyContext(null, Configuration["Data:DefaultConnection:ConnectionString"])));
Решением было изменить мой класс DbContext:
public class MyContext : IdentityDbContext<ApplicationUser>
{
private string connectionString;
public MyContext()
{
}
public MyContext(DbContextOptions options, string connectionString)
{
this.connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Used when instantiating db context outside IoC
if (connectionString != null)
{
var config = connectionString;
optionsBuilder.UseSqlServer(config);
}
base.OnConfiguring(optionsBuilder);
}
}
Однако, как предупредили несколько человек, использование DbContext в одноэлементном классе может быть очень плохой идеей. Мое использование очень ограничено в реальном коде (не в примере FunClass), но я думаю, что если вы делаете это, было бы лучше найти другие способы.
Ответ 3
Как уже упоминалось .AddDbContext
расширение .AddDbContext
добавляет его как область действия для каждого запроса. Поэтому DI не может создать экземпляр объекта Scoped
для создания объекта Singleton
.
Вы должны создать и утилизировать экземпляр MyDbContext
самостоятельно, это даже лучше, потому что DbContext должен быть утилизирован после использования как можно раньше. Для передачи строки подключения вы можете взять Configuration
из класса Startup
:
public class FunClass
{
private DbContextOptions<MyDbContext> _dbContextOptions;
public FunClass(DbContextOptions<MyDbContext> dbContextOptions) {
_dbContextOptions = dbContextOptions;
}
public List<string> GetUsers()
{
using (var db = new MyDbContext(_dbContextOptions))
{
return db.Users.Select(c=>c.UserName).ToList();
}
}
}
В Startup.cs
настроить DbContextOptionBuilder
и зарегистрировать синглтон:
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DefaultConnection"));
services.AddSingleton(new FunClass(optionsBuilder.Options));
Это немного грязно, но работает очень хорошо.
Ответ 4
Если вы хотите использовать dbContext в сервисе и вам не нужен Singleton, не используйте Singleton, а только AddTransient!
В методе ConfigureServices в моем классе запуска:
services.AddTransient<FunClass>();
В моем FunClass:
public class FunClass
{
private IServiceProvider services;
public FunClass(IServiceProvider services)
{
this.services = services;
}
public List<string> GetUsers()
{
using (var scope = services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<MyDbContext>();
var lst = dbContext.Users.Select(c=>c.UserName).ToList();
return lst;
}
}
}
Ответ 5
Нет необходимости перегружать ctor MyDbContext.
services.AddSingleton(s=>new FunClass(new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().UseSqlServer(configuration.GetConnectionString("DefaultConnection")).Options)));