multithreadingdependency-injection.net-coreconsole-applicationef-core-2.0

Console Application - DbContext instance cannot be used inside OnConfiguring


I'm using Asp.Net Core Console Application and Entiy Framework Core and Unit of Work repository pattern. When I'm using multi thread function, I get this error:

DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

UnitOfwork.cs

public interface IUnitOfWork : IDisposable
{
    void Commit();
    ApplicationDbContext GetContext();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _applicationDbContext;

    public UnitOfWork(ApplicationDbContext applicationDbContext)
    {
        _applicationDbContext = applicationDbContext;
    }

    public void Commit()
    {
        try
        {
            _applicationDbContext.SaveChanges();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

    public ApplicationDbContext GetContext()
    {
        return _applicationDbContext;
    }

    public void Dispose()
    {
        _applicationDbContext.Dispose();
    }
}

IRepository.cs

public interface IGenericRepository<T>
    where T : class, IEntity
{
    List<T> GetAll(Expression<Func<T, bool>> filter = null,
        Func<IQueryable<T>, IOrderedEnumerable<T>> orderBy = null,
        string includeProperties = "");

    T FindSingle(int id);

    T FindBy(Expression<Func<T, bool>> predicate, string includeProperties = "");

    void Add(T toAdd);

    void Update(T toUpdate);

    void Delete(int id);

    void Delete(T entity);
}

Repository.cs

public class GenericRepository<T> : IGenericRepository<T>
    where T : class, IEntity
{
    private readonly IUnitOfWork _unitOfWork;

    public GenericRepository(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public virtual List<T> GetAll(Expression<Func<T, bool>> filter = null,
        Func<IQueryable<T>, IOrderedEnumerable<T>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<T> query = _unitOfWork.GetContext().Set<T>();

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (string includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }

        return query.ToList();
    }

    public virtual T FindSingle(int id)
    {
        return _unitOfWork.GetContext().Set<T>().Find(id);
    }

    public virtual T FindBy(Expression<Func<T, bool>> predicate, string includeProperties = "")
    {
        IQueryable<T> query = _unitOfWork.GetContext().Set<T>();
        foreach (string includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }
        return query.Where(predicate).FirstOrDefault();
    }

    public virtual void Add(T toAdd)
    {
        _unitOfWork.GetContext().Set<T>().Add(toAdd);
    }

    public virtual void Update(T toUpdate)
    {
        _unitOfWork.GetContext().Entry(toUpdate).State = EntityState.Modified;
    }

    public virtual void Delete(int id)
    {
        T entity = FindSingle(id);
        _unitOfWork.GetContext().Set<T>().Remove(entity);
    }

    public virtual void Delete(T entity)
    {
        _unitOfWork.GetContext().Set<T>().Remove(entity);
    }
}

Business Services;

public interface IUserService
{
    void CreateUser(UserEntity userEntity, bool commit = false);
}
public class UserService : IUserService
{
    private readonly IGenericRepository<UserEntity> _userRepository;
    private readonly IUnitOfWork _unitOfWork;

    public UserService(IUnitOfWork unitOfWork, IGenericRepository<UserEntity> userRepository)
    {
        _unitOfWork = unitOfWork;
        _userRepository = userRepository;
    }

    public void CreateUser(UserEntity userEntity, bool commit = false)
    {
        try
        {
            _userRepository.Add(userEntity);

            if (commit)
                _unitOfWork.Commit();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Console Main.cs;

class Program
{
    public static ServiceProvider ServiceProvider;

    static void Main(string[] args)
    {
        InitializeIoc();

        Task.Run(() => { FuncA(); });

        Task.Run(() => { FuncB(); });

        Console.ReadLine();
    }

    private static void InitializeIoc()
    {
        ServiceProvider = new ServiceCollection()
            .AddDbContext<ApplicationDbContext>()
            .AddTransient<IUnitOfWork, UnitOfWork>()
            .AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>))
            .AddTransient<IUserService, UserService>()
            .BuildServiceProvider();
    }

    private static void FuncA()
    {
        var userService = ServiceProvider.GetService<IUserService>();
        for (int i = 0; i < 100; i++)
        {
            userService.CreateUser(new UserEntity { FirstName = "FuncA_" + Guid.NewGuid(), LastName = "Last", CreatedDate = DateTime.Now }, false);
        }
    }
    private static void FuncB()
    {
        var userService = ServiceProvider.GetService<IUserService>();
        for (int i = 0; i < 100; i++)
        {
            userService.CreateUser(new UserEntity { FirstName = "FuncB_" + Guid.NewGuid(), LastName = "Last", CreatedDate = DateTime.Now }, false);
        }
    }
}

How can i solve this problem?

Thank you for your help.


Solution

  • The problem is that the used AddDbContext registers your ApplicationDbContext with ServiceLifetime.Scoped, but you are not creating scopes hence it effectively works as singleton, thus is shared and accessed concurrently by multiple threads which causes the exception in question (and potentially many other because DbContext is not thread safe).

    The solution is to use scopes, e.g. call CreateScope and use returned object ServiceProvider property for resolving services:

    private static void FuncA()
    {
        using (var scope = ServiceProvider.CreateScope())
        {
            var userService = scope.ServiceProvider.GetService<IUserService>();
            // Do something ...
        }
    }
    
    private static void FuncB()
    {
        using (var scope = ServiceProvider.CreateScope())
        {
            var userService = scope.ServiceProvider.GetService<IUserService>();
            // Do something ...
        }
    }