35

I am probably not thinking in the right direction. I am fairly new to Dependency Injection and ASP.Net Core.

I have an ASP.Net core website, and one of the tasks is to import data from an excel sheet to a database that a user will upload. The excel sheets can be huge and the data transformation tasks are time-taking, hence I wish to perform them in the background. i.e. The user will upload the sheet, the response will be sent immediately and the background job/thread will import the data.

I am trying to run the background job by:

Task.Run(() => ProcessImport(model));

The problem I run into is that the Process import method calls Services that have repository classes accessing the AppDbContext via ASP.Net Dependency Injection Container that is added as Scoped and once the response is sent back, the context is disposed of. I am getting a runtime exception that you cannot use a context after it's disposed of.

My question is, what is the best way to handle this situation? Should I make the AppDbContext singleton? Should I create a new instance of AppDbContext in the ProcessImport method, and pass it along? I have read DbContext is not thread-safe, so is that a good approach?

sajadre
  • 1,141
  • 2
  • 15
  • 30
Saad Farooq
  • 977
  • 2
  • 13
  • 26
  • "Should I make the AppDbContext singleton?" Nope: https://stackoverflow.com/questions/3266295/net-entity-framework-and-transactions/3266481#3266481 – Steven Aug 23 '16 at 19:32
  • you are right, but how to deal with running tasks on a different thread, that need to access DbContext? – Saad Farooq Aug 23 '16 at 19:40
  • 3
    This is a dangerous pattern anyway. You shouldn't use an ASP.Net application to [execute long-running fire & forget tasks](http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx). If you do it right, the task is run in a process of its own and the whole DI issue is gone. – Gert Arnold Aug 23 '16 at 19:51

2 Answers2

41

You should pass IServiceScopeFactory instance (it's singleton) into your task.

Inside task, when data arrives, you should create new CreateScope() and request services from that scope. When data process finishes - dispose this scope (but hold reference to IServiceScopeFactory for next run).

See this for example. I run small and fast tasks with this library.

For heavy / long running tasks, as Gert wrote, don't rely that your task will always run to completion. Be ready for restart, be ready for re-processing of the same data.

kdaveid
  • 2,391
  • 2
  • 10
  • 14
Dmitry
  • 16,110
  • 4
  • 61
  • 73
  • I don't completely understand how to apply it in my scenario. I have a class called ImportService which is called from the controller, where I am currently injecting the repository classes to access the database. I want to use these repository classes in my ProcessImport method. If I understand correctly, If I enclose the body of the method with using (var scope = ServiceScopeFactory.CreateScope()) {} will it make the injected classes last for the lifetime of the block, or will it inject a new instance of these classes? – Saad Farooq Aug 25 '16 at 06:47
  • No, new scope will give you new instance of db (and other scope-lifetime) classes. If you don't want user [request] wait for your ProcessImport complete - you should not share scope-lifetime instances with it. Request scope is disposed when request end, which disposes all disposable content in it. – Dmitry Aug 30 '16 at 09:52
  • I used this suggestion to build a factory for creating my db context for a multi-step workflow that just kept having the services provider and db context being disposed with each request. – reckface May 15 '18 at 18:54
  • Same idea but you might want to wrap the scope creation in another class as explained in my medium post on this topic: https://medium.com/@michaelceber/net-core-web-api-how-to-correctly-fire-and-forget-database-commands-from-controller-end-point-cad7a879630 – Michael Ceber Aug 29 '20 at 22:07
5

To breakdown your questions:

  1. what is the best way to handle this situation?

    It's not ideal for the API to handle the long-running tasks. You can delegate the process to a background application

  2. Should I make the AppDbContext singleton?

    The dbContext should not be singleton in a web application scenario as it can pose problems like managing transactions.

  3. what is the best way to handle this situation?

    breakdown the various services/process:

    • The API that will take a file. Ideally the API should just take the file as input and persist it to disk or to database.
    • A service that will process the file. Create this component/service as a separate library. Then consume this library from a console app. This way you can profile the performance. Make it fire and forget method by making it async method. This way you have flexibility of reusing your code.
    • If you don't expect a lot of file uploads, you can reuse the library at your API controller and execute the method using QueueBackgroundWorkItem. See here for reference: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx
  4. Should I create a new instance of AppDbContext in the ProcessImport method, and pass it along?

    Since it is not thread-safe, create and use a separate instance of your dbContext class in each thread.

  5. I have read DbContext is not thread safe, so is that a good approach?

    For an in-depth guide of using EF dbContext, checkout this blog: http://mehdi.me/ambient-dbcontext-in-ef6/

alltej
  • 6,787
  • 10
  • 46
  • 87