C#Generic Repository - 工作单元 - 线程安全

Com*_*ity 4 c# multithreading entity-framework

我目前正在编写一个依赖于数据库的应用程序,而我正在使用Entity Framework(根据Nuget版本为6.1.1).

现在我编写了一个Repository模式,如下所示:

public class RepositoryBase<TEntity> where TEntity : class
{
    #region Constructors

    protected RepositoryBase(IDbContext context, IUnitOfWork unitOfWork)
    {
        Context = context;
        DbSet = Context.Set<TEntity>();
        UnitOfWork = unitOfWork;
    }

    #endregion

    #region Properties

    protected IDbSet<TEntity> DbSet;

    protected readonly IDbContext Context;

    protected readonly IUnitOfWork UnitOfWork;

    #endregion

    #region Methods

    protected TEntity Get(Expression<Func<TEntity, bool>> filter)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        return !query.Any() ? null : !query.Where(filter).Any() ? null : query.First(filter);
    }

    protected TEntity Get(Expression<Func<TEntity, bool>> filter, string[] includeProperties)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        includeProperties.Each(x => query = query.Include(x));

        return !query.Any() ? null : !query.Where(filter).Any() ? null : query.First(filter);
    }

    protected virtual IQueryable<TEntity> GetAll()
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        return query.AsQueryable();
    }

    protected IQueryable<TEntity> GetAll(string[] includeProperties)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        includeProperties.Each(x => query = query.Include(x));

        return query.AsQueryable();
    }

    protected IQueryable<TEntity> GetAll(Expression<Func<TEntity, bool>> filter)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        query = DbSet.Where(filter);

        return query;
    }

    protected IQueryable<TEntity> GetAll(Expression<Func<TEntity, bool>> filter, string[] includeProperties)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        query = DbSet.Where(filter);

        includeProperties.Each(x => query = query.Include(x));

        return query;
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

我确实有其他类继承自此存储库,但我暂时将它们排除在范围之外.

现在,当我在存储库中执行Get操作时,我检索数据,但是当我执行2时会在sime时间调用(我只使用相同的URL打开2个浏览器窗口并执行它们),然后弹出以下错误:

EntityFramework.SqlServer.dll中出现"System.Data.Entity.Core.EntityException"类型的异常,但未在用户代码中处理

附加信息:基础提供程序在Open上失败.

这里有一个小图片可以提供尽可能详细的信息:

在此输入图像描述

现在,我知道默认情况下Entity Framwork DbContext不是线程安全的,所以我知道我应该做些什么来使它成为线程安全的,但我只是不知道如何做到这一点.

anybode可以提供我如何实现这一目标的任何指导吗?另外,我对IDisposeable没有多少经验,所以如果你建议在某个地方实现这个,那么你应该非常友好地向我提供一些如何实现它的信息.

值得一提的是我使用Unity来管理我的对象,你可以在这里找到配置:

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
Run Code Online (Sandbox Code Playgroud)

我的类型的注册在这里完成:

public static void RegisterTypes(IUnityContainer container)
{
    container.RegisterType<IDbContext, OxygenDataContext>();
    container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());
}
Run Code Online (Sandbox Code Playgroud)

类'OxygenDataContext'是我对'DbContext'和'IDbContext'的实现.该代码可以在下面找到(剪切,我不发布我的所有元素):

public class OxygenDataContext : DbContext, IDbContext
{
    #region Constructors

    public OxygenDataContext()
        : base("Oxygen")
    {
        Configuration.ProxyCreationEnabled = false;
    }

    #endregion

    #region IDbContext Members

    public IDbSet<TEntity> Set<TEntity>() where TEntity : class
    {
        return base.Set<TEntity>();
    }

    public void SaveChanges()
    {
        base.SaveChanges();
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

更新

根据我的说法,因为我正在使用它而导致这种行为的Unity:

container.RegisterType<IDbContext, OxygenDataContext>();
Run Code Online (Sandbox Code Playgroud)

这意味着不会在每个请求上创建OxygenDataContext的新实例.

谢谢您的帮助.

更新2

当我修改Unity确认时,如下所示:

container.RegisterType<IDbContext, OxygenDataContext>(new PerRequestLifetimeManager());
Run Code Online (Sandbox Code Playgroud)

我认为只有一个单一实例,但似乎它不起作用,因为我一直收到同样的错误.

更新3

我已经更改了Unity配置以使用以下代码解析IDbContext,这是一个带有'PerResolveLifetimeManager'的DbContext:

container.RegisterType<IDbContext, OxygenDataContext>(new PerResolveLifetimeManager());
Run Code Online (Sandbox Code Playgroud)

这意味着每个解析调用应该创建一个'OxygenDataContext'的新实例,或者我不正确?

在我的UnitOfWork的构造函数中,我正在分配如下的存储库:

/// <summary>
///     Creates a new instance of the <see cref="IUnitOfWork"/>.
/// </summary>
/// <param name="context">The <see cref="IDbContext"/> in which the entities are available.</param>
public UnitOfWork(IDbContext context)
    : base(context)
{
    versioningRepository = new Repository<Versioning, IDbContext>(context, this);

    settingRepository = new Repository<Setting, IDbContext>(context, this);
    siteRepository = new VersionedRepository<Site, int, IDbContext>(context, this);
    pageRepository = new VersionedRepository<Page, int, IDbContext>(context, this);
    layoutRepository = new VersionedRepository<Layout, int, IDbContext>(context, this);
    assemblyRepository = new VersionedRepository<Assembly, int, IDbContext>(context, this);
    logRepository = new Repository<Log, IDbContext>(context, this);
}
Run Code Online (Sandbox Code Playgroud)

所以这应该意味着每个存储库都获得了它自己唯一的'IDbContext'实例,但它不能按预期工作.

更新4

我已经设法解决了我自己的问题,请耐心等待,因为我正在编写解决方案作为这篇文章的答案.

更新5

它似乎不起作用.在这里看到我认为有效的解决方案:

我调整我的IUnitOfWork接口来实现IDisposeable接口,然后重新调整实现到以下内容:

protected virtual void Dispose(bool disposing)
{
    if (disposing) { Context = null; }
}

public void Dispose()
{
    Dispose(true);
}
Run Code Online (Sandbox Code Playgroud)

但是,它不起作用.我在这里有点迷失,所以如果有人知道解决方案:-)

更新6

根据一些帖子,我需要提供一些有关如何实例化我的存储库的信息.所以这是解释:

我正在使用包含所有存储库的UnitOfWork:

/// <summary>
///     Creates a new instance of the <see cref="IUnitOfWork"/>.
/// </summary>
/// <param name="context">The <see cref="IDbContext"/> in which the entities are available.</param>
public UnitOfWork(IDbContext context)
    : base(context)
{
    versioningRepository = new Repository<Versioning>(context, this);

    settingRepository = new Repository<Setting>(context, this);
    siteRepository = new VersionedRepository<Site, int>(context, this);
    pageRepository = new VersionedRepository<Page, int>(context, this);
    layoutRepository = new VersionedRepository<Layout, int>(context, this);
    assemblyRepository = new VersionedRepository<Assembly, int>(context, this);
    logRepository = new Repository<Log>(context, this);
}
Run Code Online (Sandbox Code Playgroud)

我也尝试将代码更改为以下内容,但这也不起作用:

/// <summary>
///     Creates a new instance of the <see cref="IUnitOfWork"/>.
/// </summary>
/// <param name="context">The <see cref="IDbContext"/> in which the entities are available.</param>
public UnitOfWork(IDbContext context)
    : base(context)
{
    versioningRepository = new Repository<Versioning>(context, this);

    settingRepository = new Repository<Setting>(new OxygenDataContext(), this);
    siteRepository = new VersionedRepository<Site, int>(new OxygenDataContext(), this);
    pageRepository = new VersionedRepository<Page, int>(new OxygenDataContext(), this);
    layoutRepository = new VersionedRepository<Layout, int>(new OxygenDataContext(), this);
    assemblyRepository = new VersionedRepository<Assembly, int>(new OxygenDataContext(), this);
    logRepository = new Repository<Log>(new OxygenDataContext(), this);
}
Run Code Online (Sandbox Code Playgroud)

我仍然收到错误消息:"基础提供程序在打开时失败",内部异常显示"连接未关闭.连接的当前状态正在连接."

您会看到UnitOfWork的构造函数确实接受了一个IDbContext实例,并且所有存储库都是使用该实例构建的.

我正在使用Unity,我的IDbContext的注册采用以下代码:

container.RegisterType<IDbContext, OxygenDataContext>();
Run Code Online (Sandbox Code Playgroud)

在我的Unity配置中,我也注册了IUnitOfWork接口:

container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());
Run Code Online (Sandbox Code Playgroud)

我确实感觉错误是在Unity注册或UnitOfWork中的某个地方,但我似乎没有找到它.

Zor*_*vat 6

如果我收到你正确的信息,你就会在线程中共享相同的DbContext.这不是DbContext的用途.以这种方式共享数据非常困难.

我不喜欢以"但你为什么需要它"的形式给出答案,但这篇文章看起来就像那样.

我建议你重新设计解决方案并保持DbContext对象的生命周期 - 做一个操作并处理DbContext.执行工作单元,提交/回滚并完成存储库.不要忘记DbContext将跟踪它所提取的所有对象.您可能不希望跟踪对象在内存中的使用寿命比使用它们的事务长.

让数据库成为从多个线程共享数据的点.