Can't access scoped DbContext from root provider - c#

I get this error
System.InvalidOperationException:
'Cannot resolve scoped service 'LC.Assets.API.ApiDbFullContext' from root provider.'
Registering DbContextPool:
services.AddDbContextPool<ApiDbFullContext>(o => o.UseSqlServer(connString));
ExtensionDbHelper:
public class ExtensionDBHelper : DisposableBase, IExtensionDBHelper
{
public ExtensionDBHelper(IServiceProvider service)
{
IHttpContextAccessor http = service.GetRequiredService<IHttpContextAccessor>();
IHostingEnvironment env = service.GetRequiredService<IHostingEnvironment>();
IApiFullDbContext db = service.GetRequiredService<ApiDbFullContext>();
this.DB = db;
this.Helper = new ApiDbContextHelper(http, db, env);
this.Worker = new ApiDbUnitOfWork(this.Helper);
}
public IApiFullDbContext DB { get; }
public IApiDbContextHelper Helper { get; }
public ApiDbUnitOfWork Worker { get; }
}
UseLCCors in IApplicationBuilderExtension:
public static IApplicationBuilder UseLCCors(this IApplicationBuilder builder, Action<LCCorsOptions> option)
{
LCCorsOptions opt = new LCCorsOptions();
option.Invoke(opt);
IExtensionDBHelper helper = new ExtensionDBHelper(builder.ApplicationServices);
ICorsOriginHub hub = GenericExpressions.CorsOriginHub(helper.Worker.GetRepo<CorsOriginHub>().DbSet).FirstOrDefault(h => h.Active && h.Version.Equals(opt.Version));
if (hub == null)
{
throw new HubNotFoundException(opt.Version);
}
else if (hub.Outdated)
{
throw new IncludeHubOutdatedException(opt.Version);
}
foreach(ICorsOriginEntry entry in hub.Items.Where(itm => itm.Active))
{
builder.UseCors(options => options.WithOrigins(entry.Host).AllowAnyMethod().AllowAnyHeader());
}
return builder;
}

this solved my issue:
Changing from AddDbContextPool to plain AddDbContext
Altering the code in ExtensionDBHelper to:
IApiFullDbContext db = service.CreateScope().ServiceProvider.GetRequiredService<ApiDbFullContext>();

Related

.Net core DBContext disposes after Task.Delay

Net core repository pattern. I have below piece of code
public class MapFileUploadBusinessLogic : IMapFileUploadBusinessLogic
{
public MapFileUploadBusinessLogic(IServiceScopeFactory scopeFactory, IOptions<AuthenticationConfig> authenticationConfig, IOptions<ADFClient> AdfClient, IUnitOfWork unitOfWork, IConfiguration configuration, IMapper mapper, ILogger<MapFileUploadBusinessLogic> logger)
{
UnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
CoordinateReferenceSystemRepository = UnitOfWork.GetRepository<CoordinateReferenceSystem>();
this.Logger = logger ?? throw new ArgumentNullException(nameof(logger));
MapUploadRepository = UnitOfWork.GetRepository<MapUpload>();
azureDataFactoryRepository = unitOfWork.GetAzureRepository();
this._authenticationConfig = authenticationConfig.Value;
this._ADFClient = AdfClient.Value;
this.scopeFactory = scopeFactory;
}
public AuthenticationConfig _authenticationConfig { get; set; }
public ADFClient _ADFClient { get; set; }
public IConfiguration Configuration { get; }
private IUnitOfWork UnitOfWork { get; }
private IDbRepository<MapUpload> MapUploadRepository { get; set; }
public async Task UploadMapFile(List<MapFileUploadEntity> mapFileUploadEntities)
{
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{"Parameters", mapFileUploadEntities }
};
DataFactoryManagementClient dataFactoryManagementClient = azureDataFactoryRepository.InitiateConnection(_authenticationConfig);
var result = await azureDataFactoryRepository.RunADFPipeline(dataFactoryManagementClient, parameters, _ADFClient, ApplicationConstants.SciHubPipeline);
await Task.Delay(15000);
ADFPipeLineStatus aDFPipeLineStatus = await azureDataFactoryRepository.GetPipelineInfoAsync(dataFactoryManagementClient, _ADFClient, result);
if(aDFPipeLineStatus.Status == "Succeeded")
{
var mapUploadData = await this.MapUploadRepository.GetAsync(x => mapFileUploadEntities.Any(m => m.MapName == x.MapName)).ConfigureAwait(false);
//push notification
}
else if(aDFPipeLineStatus.Status == "Failed")
{
MapUpload mapUpload = await MapUploadRepository.GetFirstOrDefaultAsync(x => x.MapId == 9);
var mapUploadData = await this.MapUploadRepository.GetAsync(x => mapFileUploadEntities.Any(m => m.MapName == x.MapName)).ConfigureAwait(false);
//push notification
}
}
In the above code when I call await this.MapUploadRepository.GetAsync(x => mapFileUploadEntities.Any(m => m.MapName == x.MapName)).ConfigureAwait(false); it throws
Cannot access a disposed object. A common cause of this error is
disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context instances.\r\nObject name:
'MyContext'."
Is the Task.Delay making this problem? I have below registration in my startup.cs
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(this.Configuration["AzureSQLConnectionString"]));
services.AddScoped<IUnitOfWork, UnitOfWork>();
Can someone help me to understand what I am doing wrong here? Any help would be appreciated. Thanks

How to let IOptionsMonitor<T> get the latest configuration value from a running .NET Core 2.2 app hosted on an Azure Windows Server VM?

So I have a .NET Core 2.2 app running on an Azure VM with Windows Server 2019 which has the following disk configuration:
The disk on the red box is where the App files are located. When the configuration file is updated either programatically or manually, IOptionsMonitor<T> is not picking up the changes.
As stated in this link:
As mentioned in the documentation, just enabling reloadOnChange and then injecting IOptionsSnapshot<T> instead of IOptions<T> will be enough. That requires you to have properly configured that type T though.
Which I did, as shown in this code:
private IConfiguration BuildConfig()
{
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("Config.json", false, reloadOnChange: true)
.Build();
}
public async Task MainAsync()
{
AppDomain.CurrentDomain.ProcessExit += ProcessExit;
...
IServiceCollection services = ConfigureServices();
// Configures the writable options from https://github.com/Nongzhsh/Awesome.Net.WritableOptions
services.ConfigureWritableOptions<ConfigurationSettings>(_config.GetSection("configurationSettings"), "ConfigDev.json");
// ConfigurationSettings is the POCO representing the config.json contents.
services.Configure<ConfigurationSettings>(_config.GetSection("configurationSettings"));
...
}
I haven't implemented the OnChange method since I'm assuming that the values should be automatically updated once the file's contents have changed. I have also tried setting the .NET Core's DOTNET_USE_POLLING_FILE_WATCHER to true but it did not work.
Here's is my code for reading and writing values to the configuration file:
public TimeService(
IServiceProvider provider,
IWritableOptions<ConfigurationSettings> writeOnlyOptions,
IOptionsMonitor<ConfigurationSettings> hotOptions)
{
_provider = provider;
_writeOnlyOptions = writeOnlyOptions;
_hotOptions = hotOptions;
}
private async Task EnsurePostedGameSchedules()
{
DateTime currentTime = DateTime.Now;
...
# region [WINDOWS ONLY] Lines for debugging.
// _hotOptions is the depency-injected IOptionsMonitor<T> object.
if (ConnectionState == ConnectionState.Connected)
{
await debugChannel.SendMessageAsync(
embed: RichInfoHelper.CreateEmbed(
"What's on the inside?",
$"Connection State: {ConnectionState}{Environment.NewLine}" +
$"Last Message ID: {_hotOptions.CurrentValue.LatestScheduleMessageID}{Environment.NewLine}" +
$"Last Message Timestamp (Local): {new ConfigurationSettings { LatestScheduleMessageID = Convert.ToUInt64(_hotOptions.CurrentValue.LatestScheduleMessageID) }.GetTimestampFromLastScheduleMessageID(true)}{Environment.NewLine}" +
$"Current Timestamp: {DateTime.Now}",
"").Build());
}
#endregion
if (new ConfigurationSettings { LatestScheduleMessageID = _hotOptions.CurrentValue.LatestScheduleMessageID }.GetTimestampFromLastScheduleMessageID(true).Date != currentTime.Date &&
currentTime.Hour >= 1)
{
...
try
{
...
if (gameScheds?.Count > 0)
{
if (gameSchedulesChannel != null)
{
// The line below updates the configuration file.
_writeOnlyOptions.Update(option =>
{
option.LatestScheduleMessageID = message?.Id ?? default;
});
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message + Environment.NewLine + e.StackTrace);
}
}
}
And here's the config POCO:
public class ConfigurationSettings
{
public string Token { get; set; }
public string PreviousVersion { get; set; }
public string CurrentVersion { get; set; }
public Dictionary<string, ulong> Guilds { get; set; }
public Dictionary<string, ulong> Channels { get; set; }
public ulong LatestScheduleMessageID { get; set; }
public string ConfigurationDirectory { get; set; }
public DateTime GetTimestampFromLastScheduleMessageID(bool toLocalTime = false) =>
toLocalTime ?
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000).ToLocalTime() :
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000);
}
Is there anything that I still need to do in order for IOptionsMonitor<T> to pick up the config changes in the config file?
EDIT: I forgot to tell how I configured the entire app. The program by the way is a long-running .NET Core console app (not a web app) so this is how the entire program is configured:
using ...
namespace MyProject
{
public class Program
{
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult();
variables...
public async Task MainAsync()
{
AppDomain.CurrentDomain.ProcessExit += ProcessExit;
_client = new DiscordSocketClient();
_config = BuildConfig();
IServiceCollection services = ConfigureServices();
services.ConfigureWritableOptions<ConfigurationSettings>(_config.GetSection("configurationSettings"), "Config.json");
services.Configure<ConfigurationSettings>(_config.GetSection("configurationSettings"));
IServiceProvider serviceProvider = ConfigureServiceProvider(services);
serviceProvider.GetRequiredService<LogService>();
await serviceProvider.GetRequiredService<CommandHandlingService>().InitializeAsync(_config.GetSection("configurationSettings"));
serviceProvider.GetRequiredService<TimeService>().Initialize(_config.GetSection("configurationSettings"));
await _client.LoginAsync(TokenType.Bot, _config.GetSection("configurationSettings")["token"]);
await _client.StartAsync();
_client.Ready += async () =>
{
...
};
await Task.Delay(-1);
}
private void ProcessExit(object sender, EventArgs e)
{
try
{
...
}
catch (Exception ex)
{
...
}
}
private IServiceCollection ConfigureServices()
{
return new ServiceCollection()
// Base Services.
.AddSingleton(_client)
.AddSingleton<CommandService>()
// Logging.
.AddLogging()
.AddSingleton<LogService>()
// Extras. Is there anything wrong with this?
.AddSingleton(_config)
// Command Handlers.
.AddSingleton<CommandHandlingService>()
// Add additional services here.
.AddSingleton<TimeService>()
.AddSingleton<StartupService>()
.AddTransient<ConfigurationService>();
}
public IServiceProvider ConfigureServiceProvider(IServiceCollection services) => services.BuildServiceProvider();
private IConfiguration BuildConfig()
{
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("Config.json", false, true)
.Build();
}
}
}
It now worked without adding anything. I just let the app run using the compiled executable when I let my project target .NET Core 3.1. The app before was targeting .NET Core 2.2 and ran via PowerShell. I have no idea PowerShell has issues with IOptionsMonitor<T>.
According to my test, if we want to use IOptionsMonitor<T> to pick up the config changes in the config file, please refer to the following steps
My config.json
{
"configurationSettings": {
"Token": "...",
"PreviousVersion": "145.8.3",
"CurrentVersion": "145.23.4544",
"Guilds": {
"this setting": 4
},
"Channels": {
"announcements": 6
},
"LatestScheduleMessageID": 456,
"ConfigurationDirectory": "test"
}
}
My POCO
public class MyOptions
{
public string Token { get; set; }
public string PreviousVersion { get; set; }
public string CurrentVersion { get; set; }
public Dictionary<string, ulong> Guilds { get; set; }
public Dictionary<string, ulong> Channels { get; set; }
public ulong LatestScheduleMessageID { get; set; }
public string ConfigurationDirectory { get; set; }
public DateTime GetTimestampFromLastScheduleMessageID(bool toLocalTime = false) =>
toLocalTime ?
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000).ToLocalTime() :
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000);
}
Defile a class to save changes
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
public class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly IHostingEnvironment _environment;
private readonly IOptionsMonitor<T> _options;
private readonly string _section;
private readonly string _file;
public WritableOptions(
IHostingEnvironment environment,
IOptionsMonitor<T> options,
string section,
string file)
{
_environment = environment;
_options = options;
_section = section;
_file = file;
}
public T Value => _options.CurrentValue;
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
}
Implemented an extension method for ServiceCollectionExtensions allowing you to easily configure a writable options
public static class ServiceCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationSection section,
string file = "appsettings.json") where T : class, new()
{
services.Configure<T>(section);
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
return new WritableOptions<T>(environment, options, section.Key, file);
});
}
}
Please add the following code in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("Config.json", optional: false, reloadOnChange:true);
var config = configBuilder.Build();
services.ConfigureWritable<MyOptions>(config.GetSection("configurationSettings"));
...
}
Change the Json vaule
private readonly IWritableOptions<Locations> _writableLocations;
public OptionsController(IWritableOptions<Locations> writableLocations)
{
_writableLocations = writableLocations;
}
//Update LatestScheduleMessageID
public IActionResult Change(string value)
{
_writableLocations.Update(opt => {
opt.LatestScheduleMessageID = value;
});
return Ok("OK");
}
Read the JSON value
private readonly IOptionsMonitor<MyOptions> _options;
public HomeController(ILogger<HomeController> logger, IHostingEnvironment env, IOptionsMonitor<MyOptions> options)
{
_logger = logger;
_env = env;
_options = options;
}
public IActionResult Index()
{
var content= _env.ContentRootPath;
var web = _env.WebRootPath;
#ViewBag.Message = _options.CurrentValue.LatestScheduleMessageID;
return View();
}
Result
First
After change:

Asp.Net Core - How to seed data - Object reference not set to an instance of an object

After converted all my tables to start using Guid type in identity columns, I failed to seed data, so I simplified a lot the code to localize the error, and ended with a seeding class as follows:
public class SeedTest
{
private readonly MyDbContext _context;
public SeedTest(MyDbContext context)
{
_context = context;
}
public async Task SeedTest()
{
Values value1 = new Values
{
Id = Guid.Parse("29c48913-1b5c-47b8-g144-08d6d2273deb"),
ValueName = "value 1",
Created = DateTime.Now
};
_context.Values.Add(value1);
await _context.SaveChangesAsync();
}
public SeedTest()
{
}
}
This class is called from another one:
public interface IDatabaseInitializer
{
Task SeedAsync();
}
public class DatabaseInitializer : IDatabaseInitializer
{
public async Task SeedAsync()
{
SeedTest _seedTest = new SeedTest();
await _seedTest.SeedTest();
}
}
which is called from startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
private readonly IHostingEnvironment _hostingEnvironment;
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
_hostingEnvironment = env;
}
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMyDbContext<MyDbContext>(options =>
options.UseSqlServer("ConnectionStrings:MyCn"));
...
// DB Seeding
services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
...
...
}
And here is how it is triggered from program.cs
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var databaseInitializer = services.GetRequiredService<IDatabaseInitializer>();
databaseInitializer.SeedAsync().Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogCritical(LoggingEvents.INIT_DATABASE, ex, LoggingEvents.INIT_DATABASE.Name);
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Unfortunately this implementation didn't seed any data in the database, the unique error I could find is in the logs files, and it says:
System.NullReferenceException: Object reference not set to an instance
of an object. and it points to the last line of SeedTest class.
So what am I doing wrong ?
new SeedTest() does not initialize its _context field. You could use DI on your DatabaseInitializer to instantiate a SeedTest with a MyDbContext.
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly MyDbContext _context;
public DatabaseInitializer(MyDbContext context)
{
_context = context;
}
public async Task SeedAsync()
{
SeedTest _seedTest = new SeedTest(_context);
await _seedTest.SeedTest();
}
}
You are explicitly newing an instance of SeedTest in DatabaseInitialize, while the instance of DatabaseInitialize is being created by the dependency injection service. Register the SeedTest class in the services with the correct scope and let the dependency injection do its thing.
In ConfigureServices add something like
services.AddTransient<SeedTest>();
Modify DatabaseInitializer
public class DatabaseInitializer : IDatabaseInitializer{
private readonly SeedTest _seedTest;
public DatabaseInitializer(SeedTest seedTest)
{
_seedTest = seedTest;
}
public async Task SeedAsync()
{
await _seedTest.SeedTest();
}
}
Remove the parameterless SeedTest constructor and make sure the MyDbContext type registered is what is passed in the other constructor as you have both MyDbContext and DbContext.
You can try this, i have used .net core 2.2 for this sample -
MyDbContext.cs
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
Database.EnsureCreated();
}
public DbSet<Values> Values { get; set; }
}
SeedTest.cs
public class SeedTest
{
private readonly MyDbContext _context;
public SeedTest(MyDbContext context)
{
_context = context;
}
public async Task SeedTest1()
{
Values value1 = new Values
{
Id = Guid.Parse("AFE1052A-A694-48AF-AA77-56D2D945DE31"),
ValueName = "value 1",
Created = DateTime.Now
};
_context.Values.Add(value1);
var value = await _context.SaveChangesAsync();
}
public SeedTest()
{
}
}
Service
public interface IDatabaseInitializer
{
Task SeedAsync();
}
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly MyDbContext _cotext;
// Inject DbContext
public DatabaseInitializer(MyDbContext dbContext)
{
_cotext = dbContext;
}
public async Task SeedAsync()
{
// Object with contructor which having DbContext parameter
SeedTest _seedTest = new SeedTest(_cotext);
await _seedTest.SeedTest1();
}
}
startup.cs
services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
services.AddDbContext<MyDbContext>(option=> option.UseSqlServer("Data Source=localhost;Initial Catalog=StackOverFlow1;Integrated Security=True"));
program.cs
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var databaseInitializer = services.GetRequiredService<IDatabaseInitializer>();
databaseInitializer.SeedAsync().Wait();
}
catch (Exception ex)
{
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
You can read more about seed data.

Getting HubContext as null in .NET Core

I am working with .NET core application using SignalR. My hub class code is:
public class LiveDataHub : Hub
{
public async Task GetUpdatedDataFromServer()
{
try
{
var dal = new DAL();
var dashboardVM = dal.GetDashboardViewModels();
Clients.Caller.SendAsync("UpdatePortalWithUpdatedData", dashboardVM);
}
catch(Exception ex)
{
}
}
}
My Startup.cs code is:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSignalR(routes =>
{
routes.MapHub<LiveDataHub>("/LiveDataHub");
});
}
I have another class "ModuleLoader", whose code is:
public class ModuleLoader
{
GlobalCache _globalCache = GlobalCache.GetInstance();
private readonly IHubContext<LiveDataHub> _hubContext;
public ModuleLoader()
{
}
public ModuleLoader(IHubContext<LiveDataHub> hubContext)
{
_hubContext = hubContext;
}
private void OnAdapterGroupDataReceived(DeviceAdapterGroup deviceAdapterGroup)
{
var dal = new DAL();
dal.InsertOrUpdateAllAdapters(deviceAdapterGroup.AdapterGroup);
if(deviceAdapterGroup != null)
{
dal.InsertAllDeviceAdapter(deviceAdapterGroup);
}
var allAdapters = dal.GetAllAdaptersConnectedToDevice(deviceAdapterGroup.DeviceId);
var adaptersToDelete = allAdapters.Except(deviceAdapterGroup.AdapterGroup.Select(x => x.AdapterId)).ToList();
if (adaptersToDelete != null && adaptersToDelete.Count > 0)
dal.DeleteAllAdapters(adaptersToDelete);
var dashboardVM = dal.GetDashboardViewModels();
_hubContext.Clients.All.SendAsync("UpdatePortalWithUpdatedData", dashboardVM);
}
}
Issue is that when i run this code, i get the exception that _hubContext is null. How can i resolve it. Any help would be much appreciated
You might also need to add your ModuleLoader class into a DI container if you haven't already. You can use the .net core default container as shown below:
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddScoped<ModuleLoader>();
}

EF 7 (Core). Create DBContext like AddTransient

According to documents when I configure DbContext like below DI register it in scope (per http request)
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
The problem appears when I am trying to access it in another thread.
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();
}
}
I want to configure DbContext creation per each call (AddTransition). It would give me possibility to write next code
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();
}
}
So now if I want to create DBContext per request I will use IUnitOfWorkFactoryPerRequest, and when I want to use DBContext in some background thread I can use IUnitOfWorkFactoryPerCall.
My temporary solution.
I created singleton which can create Context "in transient way"
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);
}
}
Then I call init method in Startup Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AppDependencyResolver.Init(app.ApplicationServices);
//other configure code
}
And after all I can call AppDependencyResolver.Current.CreateUoWinCurrentThread() in some background thread.
If someone can provide more elegant solution I will be appreciated.
Within your controller, why are you trying to inject into private readonly DBData _context;? If you've registered your IUnitOfWorkFactoryPerCall via DI, you should be injecting that into your controller I believe? You then access your context via the interface.
To expand, this is what I am suggesting you do:
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();
}
}

Categories

Resources