Использовать 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)));