3

I am using ASP.net core. I have problem with implementing dbcontext into singleton.

I need my singleton IModuleRepository to be running right after start of the project. So I am creating new instance of this dependency in public void ConfigureServices(IServiceCollection services) in Startup.cs file.

This singleton is using another singleton, so I am using it like this:

services.AddDbContext<ModulesDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).EnableSensitiveDataLogging());
...    
services.AddSingleton<IModuleRepository, ModuleRepository>();
services.AddSingleton<ICommunicationRepository>(new CommunicationRepository(services.BuildServiceProvider().GetService<IModuleRepository>()));

In ModuleRepository I am using DBcontext.

// Db context
    private readonly ModulesDbContext _modulesDbContext;

    public ModuleRepository(ModulesDbContext modulesDbContext)
    {
        _modulesDbContext = modulesDbContext;
    }

When I am calling _modulesDbContext.SomeModel.ToList(); I get error:

System.InvalidOperationException: An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point.

How to avoid this error when I need this singleton to run after the project is started?

Thank you for your help.

David Pavelka
  • 373
  • 1
  • 5
  • 11

2 Answers2

2

As @Ilya Chumakov commented, you could just tell the DI container to use your concrete class like so:

services.AddSingleton<ICommunicationRepository, CommunicationRepository>();

Then any class can depend on ICommunicationRepository and get the concrete repository, which gets the Db context.

juunas
  • 54,244
  • 13
  • 113
  • 149
  • I know I can do this. But what will start this singleton instead of Startup? I need to run constructor, where I am creating new Task which is running in the background and communicate with Modbus protocol periodically. So I need to kick it on the start, not in the Controller. – David Pavelka May 02 '17 at 08:57
  • How about moving the Task kickoff to a method, resolving the communication repository from the service provider and calling the method? Also, you could look into task scheduling libraries to run this kind of things. – juunas May 02 '17 at 09:07
  • You can also resolve dependencies in the `Configure` method and run the startup task from there. That actually would make way more sense than running it in a constructor. – juunas May 02 '17 at 09:10
  • It makes no change when I move it to the method. It is by principe the same thing as having it in constructor. I do NOT have problem with running Task. I have problem with DBContext. – David Pavelka May 02 '17 at 09:24
  • True.. Maybe you should try the approach of injecting `ICommunicationRepository` as a function parameter to `Configure`? I run database migrations from there usually so I can say that the Db context should work at that point. – juunas May 02 '17 at 09:25
  • And about scheluding libraries - I made it myself, because there is no library that support timing in ms. Also I need to have controll above this code. But this is out of scope - my problem is DbContext. How to get it to the singleton in configure method in Startup, if this error is showing. – David Pavelka May 02 '17 at 09:27
  • I have tried it, but same result. But I think that it should work. I am confused why is it not. – David Pavelka May 02 '17 at 09:29
  • Hmm, sorry I couldn't help you. I'll have to try this later today. – juunas May 02 '17 at 09:31
-1

I figured out this problem. This calling of the dependencies were right. The error is that in CommunicationRepository I've created 2 Task and both of them were using the same DbContext - so it was using it multiple times. I had to say task.Wait();

Full code in constructor of the CommunicationRepository after correction:

// Add pernament communication
        var task = new Task(AddPernamentCommunicationAll);
        task.Start();
        task.Wait();

        // Add modules
        var taskModules = new Task(AddModulesToList);
        taskModules.Start();

Thank you for your answers.

David Pavelka
  • 373
  • 1
  • 5
  • 11
  • 2
    Tasks are not threads, just call your code sync. Also, do not run async tasks in Constructor, it's pretty bad. consider using a factory to create + initialize an object instead. Also that being said: EF Core isn't thread safe. You can't use the same context from two threads (means: You can't paralellize the operations), so yo shouldn't use it as singleton (two requests could be accessing it, then you receive an exception!) – Tseng May 02 '17 at 10:28
  • I know that Tasks is something different than threads. I think that running async in constructor is totally ok in my case. I am not new to these things. In my case it is OK, because if there were no tasks, my server won't be accessible if something went wrong in this code. Yes, I figgured out, that EF core is not thread safe - whole answer is about this. I think that you are out of the context. I have answered my own question, so why is it -1 if this is working? – David Pavelka May 02 '17 at 11:25
  • David, the way you run the first and second task makes no sense at all. You create a task by passing an action to it. One only does that if one wants to run the `AddPernamentCommunicationAll` on another thread. This makes no sense and is bad in ASP.NET/ASP.NET Core as you are playing with the threadpool, lowering efficiency of your application since you create a new thread to "release" the request thread, you still use only a single thread. You basically gain nothing from it, except the overhead of switching thread/task contexts. – Tseng May 02 '17 at 11:31
  • ASP.NET Core works differently than WPF/Desktop applications, when it comes to threads. See [this](http://msdn.microsoft.com/en-us/magazine/dn802603.aspx) article (its for ASP.NET but still valid for ASP.NET Core) and especially the "What About the Thread Doing the Asynchronous Work?" section to understand why it's absolutely pointless to start non-IO bound tasks in ASP.NET Core. – Tseng May 02 '17 at 11:37
  • 2
    Also, async in constructor is **VERY BAD**. When the constructor is finished you are expected that the class is initialized, but when you run an async task while accessing the class it may result in an invalid operation exception, i.e. if in constructor you start a EF db request and then call a method which also calls a db request you get an exception because you try to execute two operations at the same time in parallel with your context, which doesn't work or access a value which isn't loaded yet. If you need to populate some values form database, consider using a factory to initialize it – Tseng May 02 '17 at 11:39
  • Last but not least, constructors are supposed to be fast, so you shouldn't run long running operations within it anyways. But if you make new question, I may post you an example on how to do initialization in a factory. – Tseng May 02 '17 at 11:43
  • Ok, I will read more about this and maybe I wil contact you if I will need. Thank you. – David Pavelka May 02 '17 at 12:23
  • @Tseng Hi, maybe you can answer this question, which is connected with this problem - http://stackoverflow.com/questions/43865493/communication-in-asp-net-core – David Pavelka May 09 '17 at 09:13