2

The application was developed on ASP NET Core 3. To log user actions, I decided to use a single method in the Project class. Faced the problem of using one singleton dbContext from different threads.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    string connection = Configuration.GetConnectionString("ConnectionDB");
    services.AddDbContext<DataBaseContext>(options => options.UseSqlServer(connection), ServiceLifetime.Transient, ServiceLifetime.Singleton);

    services.AddSingleton<Project>();
}

Project.cs

public async Task AddUserLog(string action, string message, int userId)
{
    try
    {
        UserLog userLog = new UserLog()
        {
            Action = action,
            Message = message,
            UserId = userId
            Datepoint = DateTime.Now
        };

        _dbContext.UserLog.Add(userLog);
        await _dbContext.SaveChangesAsync();
    }
    catch (Exception ex)
    {
        await AddSystemLog("Project", "AddUserLog", ex.Message);
    }
}

SchemeController.cs

public class SchemeController : ControllerBase
{
    private readonly Project _project;

    public SchemeController(Project project)
    {
        _project = project;
    }

    [Authorize(Policy = "AdvancedControl")]
    [HttpPost("[action]")]
    public async Task SomeMethode()
    {
        for (int i = 0; i < 10; i++)
        {
            await _project.AddUserLog("Text", "Message", 42);
        }       
    }
}

Already at the second iteration of the loop, I catch an exception in the AddUserLog method: "A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext."

I suggest several solutions:

  1. Add the log to the buffer table and then save it to the database by timer. But this is not the best way out;

  2. Block the method while it is being saved to the database.

But I don’t like any of the options.

Please tell me the correct approach in solving this issue.

3
  • Never use DbContext like this. Do you really need Project to be a singleton? Commented Nov 11, 2019 at 13:20
  • @DavidG The 'project' also performs other tasks. Logging is one of the tasks that I wanted to implement in it. Why do not recommend? Commented Nov 11, 2019 at 13:26
  • Because if you make it a singleton, you capture the DbContext permanently. Commented Nov 11, 2019 at 13:30

1 Answer 1

3

So, you trying to use shared resource (singleton Project class) to perform parallel operations (save UserLogs) while your shared resource implementation is not thread-safe (exceptions raised).

You have at lease three ways to solve this:

  1. Do not use shared resource: register Project per scope instead of singletone;
  2. Do not perform operations in parallel: seems hard to achieve because you making webapp and you can't force user(s) to wait
  3. Refactor your resource to be thread-safe: add locks/mutexes/buffering... inside Project

There is no one "correct" way - all 3 are correct. Choose one you like (or combine several).

Usually using scoped dbcontext is recommended (because connections are pooled), but it's the creator of app who should decide.

Sign up to request clarification or add additional context in comments.

5 Comments

The Project class is singleton. And if I'm not mistaken, then it is possible to use dbcontext for it too, only singleton. I will create a preliminary buffer table and block it while archiving to the database.
Making DbContext singletone will make it singletone for all other usages (other controllers). You will receive same errors (second operations started...) in your "regular" controllers.
Tell me please how to get dbContext for Project class correctly?
(1) use scoped; (2) make 2 dbcontext - one singleton for UserLog table, second scoped for other tables; (3) obtain IScopeServiceFactory and create your own scope inside Project. And you still need invent your own thread-safety for 2 and 3.
Thanks for the help. I put common buffer tables into a singleton class. I rewrote the Project class so that now it is scoped and is called once at the start of the project.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.