ЭФ 7 (основной). Создайте DBContext, например AddTransient

Согласно документам, когда я настраиваю DbContext, как показано ниже, я регистрирую его в области действия (для каждого HTTP-запроса)

services.AddEntityFramework()
   .AddSqlServer()
   .AddDbContext<DBData>(options => {
        options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);                    
    }
);

Проблема возникает, когда я пытаюсь получить к ней доступ в другом потоке.

public class HomeController : Controller
{
    private readonly DBData _context;

    public HomeController(DBData context)
    {
        _context = context;
    }

    public IActionResult StartInBackground()
    {
        Task.Run(() =>
            {
                Thread.Sleep(3000);
                //System.ObjectDisposedException here
                var res = _context.Users.FirstOrDefault(x => x.Id == 1);
            });

        return View();
    }
}

Я хочу настроить создание DbContext для каждого вызова (AddTransition). Это дало бы мне возможность написать следующий код

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<DBData>(options => {
                //somehow configure it to use AddTransient
                options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);                    
                }
            );

        services.AddTransient<IUnitOfWorkFactoryPerCall, UnitOfWorkFactory>();
        services.AddScoped<IUnitOfWorkFactoryPerRequest, UnitOfWorkFactory>();

        services.AddMvc();
    }

    public interface IUnitOfWorkFactoryPerCall : IUnitOfWorkFactory { }
    public interface IUnitOfWorkFactoryPerRequest : IUnitOfWorkFactory { }

    public interface IUnitOfWorkFactory : IDisposable
    {
       DBData Context { get; }
    }

    public class UnitOfWorkFactory : IUnitOfWorkFactoryPerCall, IUnitOfWorkFactoryPerRequest
    {
        public UnitOfWorkFactory(DBData context)
        {
            Context = context;
        }

        public DBData Context
        {
            get; private set;
        }

        public void Dispose()
        {
            Context.Dispose();
        }
    }

Так что теперь, если я хочу создать DBContext для каждого запроса, я буду использовать IUnitOfWorkFactoryPerRequest, а когда я хочу использовать DBContext в каком-то фоновом потоке, я могу использовать IUnitOfWorkFactoryPerCall.


person YuriyP    schedule 22.01.2016    source источник
comment
у тебя есть новости? имея аналогичную задачу по использованию DbContext в другом потоке.   -  person slaesh    schedule 15.03.2016


Ответы (2)


Мое временное решение. Я создал синглтон, который может создавать контекст "переходным способом"

public class AppDependencyResolver
{
    private static AppDependencyResolver _resolver;

    public static AppDependencyResolver Current
    {
        get
        {
            if (_resolver == null)
                throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
            return _resolver;
        }
    }

    public static void Init(IServiceProvider services)
    {
        _resolver = new AppDependencyResolver(services);
    }

    private readonly IServiceProvider _serviceProvider;

    public AppDependencyResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IUnitOfWorkFactory CreateUoWinCurrentThread()
    {
        var scopeResolver = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
        return new UnitOfWorkFactory(scopeResolver.ServiceProvider.GetRequiredService<DBData>(), scopeResolver);
    }
}

Затем я вызываю метод init в методе Startup Configure.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    AppDependencyResolver.Init(app.ApplicationServices);
    //other configure code
}

И ведь я могу вызвать AppDependencyResolver.Current.CreateUoWinCurrentThread() в каком-то фоновом потоке.

Если кто-то может предоставить более элегантное решение, я буду признателен.

person YuriyP    schedule 17.03.2016

В вашем контроллере, почему вы пытаетесь внедрить в private readonly DBData _context;? Если вы зарегистрировали свой IUnitOfWorkFactoryPerCall через DI, я полагаю, вы должны ввести это в свой контроллер? Затем вы получаете доступ к своему контексту через интерфейс.

Чтобы расширить, вот что я предлагаю вам сделать:

public class HomeController : Controller
{
    private readonly IUnitOfWorkFactoryPerCall _contextFactory;

    public HomeController(IUnitOfWorkFactoryPerCall contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public IActionResult StartInBackground()
    {
        Task.Run(() =>
            {
                Thread.Sleep(3000);
                //System.ObjectDisposedException here
                var res = _contextFactory.Context.Users.FirstOrDefault(x => x.Id == 1);
            });

        return View();
    }
}
person Ian Auty    schedule 22.01.2016
comment
Это не сработает, потому что DBData все еще разрешается в области видимости. Итак, весь мой вопрос о том, как настроить создание DBContext для каждого вызова. - person YuriyP; 23.01.2016