Parallel.Foreach throws exception InvalidOperationException

102 Views Asked by At

When i am using Parallel.Foreach loop on the collection then it throws exception "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, however, instance members are not guaranteed to be thread-safe"

I have onion architecture using .netcore 3.1 and efcore 2.2. Using Generic repository with unitofwork design pattern

  • Created a factory for unitofwork and added a new method createFactoryContext.

    public interface IUnitOfWorkFactory { UnitOfWork CreateUnitOfWork(); }

     public class UnitOfWorkFactory : IUnitOfWorkFactory
     {
       private readonly YourDbContext _dbContext;
    
       public UnitOfWorkFactory(YourDbContext dbContext)
       {
          _dbContext = dbContext;
       }
    
       public UnitOfWork CreateUnitOfWork()
       {
          return new UnitOfWork(_dbContext);
       }
     }
    

    `

  • Registered in startup.cs file

    services.AddScoped<IUnitOfWorkFactory, UnitOfWorkFactory>();

  • Injected in Service class file Created a object of unitofwork on each ` public class MyService { private readonly IUnitOfWorkFactory _unitOfWorkFactory;

     public YourService(IUnitOfWorkFactory unitOfWorkFactory)
     {
         _unitOfWorkFactory = unitOfWorkFactory;
     }
    
     public void SendEmails(UserModel user)
     {
         using (var unitOfWork = _unitOfWorkFactory.CreateUnitOfWork())
         {
           Templates emailTemplate;
           if (user.userType = 1)
           {
            emailTemplate = unitOfWork.Templates.GetAllWithNoTracking().Single(x => x.TemplateName= "abc");
           }
           else 
           {
            emailTemplate = unitOfWork.Templates.GetAllWithNoTracking().Single(x => x.TemplateName= "xyz");
           }               
         }
     }
    

    } `

  • Controller Code

    Parallel.ForEach(result, obj => { _myService.SendEmail(obj); });

What could be the possible solution

1

There are 1 best solutions below

5
JonasH On

Your UnitOfWorkFactory should create a new dbContext whenever CreateUnitOfWork is called. Something like

public class UnitOfWorkFactory : IUnitOfWorkFactory
 {
   private readonly string _connectionString;

   public UnitOfWorkFactory(string connectionString)
   {
      _connectionString= connectionString;
   }

   public UnitOfWork CreateUnitOfWork()
   {
      return new UnitOfWork(new YourDbContext (_connectionString));
   }
 }

This way each call to SendEmails will use different DbContext objects, and therefore avoid concurrent usage of any context. The framework should pool connections to the database automatically, thus keeping creation of DbContext objects fairly cheap.

However, running database queries in a parallel loop like this is usually not a great idea. If you can, you should usually do as much work as possible work in a single db query, i.e. add a method like void SendEmails(IEnumerable<UserModel> users). Since the example is incomplete and does nothing, it is difficult to provide concrete recommendations.