【问题标题】:C# Generic Repository - Unit Of Work - Thread SafetyC# 通用存储库 - 工作单元 - 线程安全
【发布时间】:2014-09-26 23:35:34
【问题描述】:

我目前正在编写一个依赖于数据库的应用程序,并且我正在使用实体框架(根据 Nuget 的版本为 6.1.1)。

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

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
}

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

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

“System.Data.Entity.Core.EntityException”类型的异常 发生在 EntityFramework.SqlServer.dll 中但未在用户中处理 代码

附加信息:底层提供程序在打开时失败。

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

现在,我知道实体框架 DbContext 默认情况下不是线程安全的,所以我知道我应该做一些事情来使其成为线程安全的,但我只是不知道该怎么做。

任何人都可以就如何实现这一目标向我提供任何指导吗? 另外,我对 IDisposeable 没有太多经验,所以如果您建议在某个地方实现它,您应该非常好心地向我提供一些有关如何实现它的信息。

值得一提的是,我正在使用 Unity 来管理我的对象,您可以在此处找到其中的配置:

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

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

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

“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
}

更新

据我说,是 Unity 导致了这种行为,因为我正在使用:

container.RegisterType<IDbContext, OxygenDataContext>();

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

感谢您的帮助。

更新 2

当我像下面这样修改 Unity 确认时:

container.RegisterType<IDbContext, OxygenDataContext>(new PerRequestLifetimeManager());

我坚持认为只有 1 个单一实例,但它似乎无法正常工作,因为我不断收到相同的错误。

更新 3

我已更改 Unity 配置以解析 IDbContext,它是一个带有“PerResolveLifetimeManager”的 DbContext,使用以下代码:

container.RegisterType<IDbContext, OxygenDataContext>(new PerResolveLifetimeManager());

这意味着每个解析调用都应该创建一个“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);
}

所以这应该意味着每个存储库都获得了它自己唯一的“IDbContext”实例,但是它并没有按预期工作。

更新 4

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

更新 5

它似乎不起作用。在这里查看我认为可行的解决方案:

我调整了 IUnitOfWork 接口以实现 IDisposeable 接口,然后将实现重新调整为以下内容:

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

public void Dispose()
{
    Dispose(true);
}

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

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

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

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

我仍然收到错误消息:“底层提供程序在打开时失败”,内部异常显示“连接未关闭。连接的当前状态为正在连接。”

您会看到 UnitOfWork 的构造函数确实采用了 IDbContext 实例,并且所有存储库都是使用该实例构造的。

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

container.RegisterType<IDbContext, OxygenDataContext>();

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

container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());

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

【问题讨论】:

  • 内部异常是什么?您的图像模糊,很难阅读内部异常。
  • 确实,图像有点模糊。内部异常是:“连接未关闭。连接的当前状态为正在连接。”
  • 您不是偶然在线程之间共享同一个存储库吗?因为您对 DbContext 注册所做的一切都很好。您如何解析存储库本身?
  • 对不起,我不太明白你的问题,我发布的更新不是很清楚吗?
  • 只显示创建和使用存储库的代码。我相信这就是问题所在。

标签: c# multithreading entity-framework


【解决方案1】:

如果我的信息正确,那么您将在线程之间共享相同的 DbContext。这不是 DbContext 的用途。以这种方式共享数据非常困难。

我不喜欢以“但你为什么需要它”的形式给出答案,但这篇文章看起来就是这样。

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

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

【讨论】:

  • 感谢您的回答。我确实知道共享上下文是一个坏主意,但我正坐在 Microsoft Unity 之间,这似乎对我不利:)
  • 我明白了。我使用的一种方法是创建 Repository,其中 T 是 IDbContext。然后 Repository 实例化 T。这样,IDbContext 的生命周期等于 Repository 对象的生命周期。
  • 我已经用更新 2 更新了我的问题,因此现在 Unity 在“PerRequestLifetimeManager”上解析了我的 DbContext,但它似乎不起作用。我确实需要这种东西来简化单元测试......我只是不明白为什么 Unity 不管理这种东西......
  • 当我采用你的方法时,存储库 where T : IDbContext,Unity 应该如何解析 IDbContext 接口,以及 PerRequest 或没有或可能有其他东西?
  • 那里不再有 Unity。相反,您实例化 Repository,以便存储库和数据上下文的生命周期变得相同。 IDbContext 不再通过构造函数接收。在相关说明中,您可能必须定义一个更具体的接口,例如 IOxygenDataContext,它扩展了 IDbContext。
【解决方案2】:

您可能没有在此处完全处理上下文:

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

public void Dispose()
{
    Dispose(true);
}

我的理解是,当您创建 dbContext 的抽象(如您的 UoW)时,您希望明确调用垃圾收集器以确保您已清理未使用的资源。此外,您需要处理 dbContext 而不是将其设置为 null。

例子:

    private bool disposed = false;

    protected virtual void Dispose(bool disposing) 
    {
        if (!this.disposed) 
        {
            if (disposing)
               context.Dispose(); // <-- dispose
        }
        this.disposed = true;
    }

    public void Dispose() 
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

请试一试 :) - 希望对您有所帮助。

【讨论】:

  • 这似乎没有帮助。我觉得我错过了一些重要的代码,但我不太确定它在哪里。也许我最好的选择是从头开始重写代码。我已经厌倦了寻找问题...
  • 抱歉没用 :(。问题是你没有正确处理你的 dbContext 我猜这是因为你的代码中从来没有一个新的上下文实例。大概,这应该是通过注入处理,但尝试将 IDbContext 替换为混凝土,看看是否还有问题。
  • 您希望我在哪里替换该代码?在单位工作?
  • 是的。让您的工作单元实例化一个新的上下文实例,而不是注入它。我猜你在处理上下文时不会遇到同样的问题。它应该可以帮助您确定原始代码有什么问题。
【解决方案3】:

问题在于您如何注册 IDbContext 实例。 DbContext 不是线程安全的,因此您应该在每个请求/线程中解析一次(与 Repository 和 UnitOfWork 相同),因此将您的注册代码更改为:

container.RegisterType<IDbContext, OxygenDataContext>(new PerRequestLifetimeManager());

【讨论】:

  • 这似乎也不起作用。我认为这个缺陷在其他地方,但我无法找到它......
  • 它没有帮助的原因是您有多个问题叠加在一起。现在您已经正确设置了 IoC,向我们展示一个实际使用 IUnitOfWork 的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-25
相关资源
最近更新 更多