Entity Framework Core 线程安全吗?

8

我遵循ASP.NET Core中的通用存储库模式,但在IRepository上,我使用IQueryable而不是IEnumerable

public  interface IRepository<T> where T: BaseEntity
{
    IQueryable<T> Table { get; }
    IEnumerable<T> TableNoTracking { get; }
    T Get(long id);
    void Insert(T entity);
    void Update(T entity);
    void Delete(T entity);
}

以及实现类:

    public class EFRepository<T> : IRepository<T> where T : BaseEntity
    {
        private readonly ApplicationDbContext _ctx;
        private DbSet<T> entities;
        string errorMessage = string.Empty;

        public EFRepository(ApplicationDbContext context)
        {
            this._ctx = context;
            entities = context.Set<T>();
        }

        public virtual IQueryable<T> Table => this.entities;
    }

服务类:

public class MovieService : IMovieService
{
        private readonly IRepository<MovieItem> _repoMovie;

        public MovieService(IRepository<MovieItem> repoMovie)
        {
            _repoMovie = repoMovie;
        }

        public async Task<PaginatedList<MovieItem>> GetAllMovies(int pageIndex = 0, int pageSize = int.MaxValue,
               IEnumerable<int> categoryIds = null)
        {
            var query = _repoMovie.Table;

            if (categoryIds != null)
            {
                query = from m in query
                        where categoryIds.Contains(m.CategoryId)
                        select m;
            }

            return await PaginatedList<MovieItem>.CreateAsync(query, pageIndex, pageSize);
        }
}

在 Startup.cs 中:
  public void ConfigureServices(IServiceCollection services)
  {
        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddMvc();
        services.AddScoped(typeof(IRepository<>), typeof(EFRepository<>));           
        services.AddTransient<IMovieService, MovieService>();
        services.AddTransient<ICategoryService, CategoryService>();
    }

这段代码会抛出一个错误:

InvalidOperationException: 在前一个操作完成之前,此上下文启动了第二个操作。任何实例成员都不能保证是线程安全的。

如果我将 IRepository 切换回 IEnumerable,那么它就可以正常运行。

有没有办法让它使用 IQueryable 正确地运行EF Core?

query = from m in query
        where categoryIds.Contains(m.CategoryId)
        select m;

我猜在 services.AddScoped(typeof(IRepository<>), typeof(EFRepository<>)); 和 services.AddTransient<IMovieService, MovieService>(); 之间可能会有冲突,这将调用另一个 EF 上下文。如果我只是放弃服务类并直接使用 Repository,则没问题。 - nam vo
1个回答

9

Entity Framework DbContext 不是线程安全的。您一次只能执行一个查询,否则将会出现上述异常。

我猜想您在同一请求中并行使用了多次存储库,这就是为什么您会遇到异常的原因。如果您不为每个请求创建一个事务,可以将存储库设置为短暂(transient)。在这种情况下,每个服务实例都将创建一个新的存储库,从而避免并发问题。


谢谢你的帮助。我尝试了 services.AddTransient(typeof(IRepository<>), typeof(EFRepository<>)); 但仍然失败,不确定如何解决。我刚刚开始这个项目,只写了几行代码,现在卡住了 :D - nam vo
我发现问题出在 categoryIds.Contains(m.CategoryId) 这一行。如果我将 categoryIds.ToList()(从 IEnumerable 转换而来)则可以正常运行。 - nam vo
@namvo,如果你从数据库中单独查询categoryIds,那就可以解释并发了。很好,你找到了。 - Andrii Litvinov
1
谢谢,我花了一整天的时间解决这个问题,将我的存储库中的AddScoped更改为AddTransient解决了这个问题。 - Nikola Gaić
@A.Rowan 不,我没有记得有任何问题。虽然一些开发人员更喜欢每个请求创建一个SQL事务。在这种情况下,同一事务内的多个并发连接将升级为分布式事务协调器 (DTC),这在大多数情况下是不希望的,因为会增加性能和配置开销。 - Andrii Litvinov
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接