Консольное приложение - экземпляр DbContext нельзя использовать внутри OnConfiguring - PullRequest
0 голосов
/ 02 октября 2018

Я использую консольное приложение Asp.Net Core и шаблон репозитория Entiy Framework Core и Unit of Work.Когда я использую многопоточную функцию, я получаю эту ошибку:

Экземпляр DbContext нельзя использовать внутри OnConfiguring, так как он все еще настраивается на этом этапе.Это может произойти, если вторая операция запускается в этом контексте до завершения предыдущей операции.Ни один из членов экземпляра не гарантированно является потокобезопасным.

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);
    }
}

Бизнес-услуги;

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);
        }
    }
}

Как мне решить эту проблему?

Спасибо за вашу помощь.

1 Ответ

0 голосов
/ 02 октября 2018

Проблема в том, что используемый AddDbContext регистрирует ваш ApplicationDbContext с ServiceLifetime.Scoped, но вы не создаете области действия, следовательно, он эффективно работает как синглтон, поэтому совместно используется и доступен одновременно нескольким потокам, что вызывает рассматриваемое исключение(и, возможно, многие другие, потому что DbContext не является потокобезопасным).

Решение состоит в том, чтобы использовать области, например, вызов CreateScope и использовать возвращенное свойство объекта ServiceProvider для разрешения служб:

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

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