没有带数据库事务或 TransactionScope 的 EF 的 C# UnitOWork 实现

标签 c# entity-framework transactions asp.net-web-api transactionscope

我正在从事一个 Web API 项目(ninject、原始 sql 查询、存储库模式、UoW 模式)我几乎在所有地方都检查过一篇文章,该文章描述了使用不使用 Entity Framework 的简单数据库事务实现 UoW(纯 SQL 请求和 SqlConnection 等...),但找不到任何内容。

我遇到的问题如下。我有一个 Web API,它具有与存储库一起工作的 Controller ,而存储库又通过 UoW 注入(inject)到它们中的 DBManager 类来处理数据库。

让我们假设我在存储库中有 2 个方法,每个方法都更新数据库中的数据:

方法 1 - 更新工单(添加客户的帖子)。 方法 2 - 更新工单的状态(仅当发布成功时)。

这些方法可以一个接一个地调用,或者单独调用,例如,可以从某个其他方法调用方法 2,例如,在票证关闭之后。

方法一,在更新DB之前通过DBManager创建一个事务。然后它更新 DB 并调用 Method2 来做他的事情。方法 2,因为它也可以作为独立方法调用,所以也在更新数据库之前启动事务。执行查询时,它会提交事务并返回到 Method1。 Method1 在这个阶段也提交事务,因为没有异常,它希望保留对数据库所做的更改。但是,它不能,因为更改已由 Method2 提交。

所以 Action 图类似于下面的图:

Method1()
  DBManager.BeginTransaction() - begins new transaction

  update DB - adds post to the ticket

  Method2() - calls method 2 to update ticket status
    DBManager.BeginTransaction() - returns transaction started by Method1()

    update DB - updates ticket status

    DBManager.CommitTransaction() - commits transaction

    return

  DBManager.CommitTransaction() - commits transaction to save ALL changes but can't since transaction was already committed.

如果我需要在票证状态更新后调用其他方法,那么方法将使用全新的数据集,因为更改已通过 Method2() 提交到数据库中。

我开始考虑如何解决这个问题,但找不到任何东西。我已经阅读了有关 TransactionScope 的内容,并认为我可以做这样的事情:

public class UnitOfWork : IUnitOfWork, IDisposable
{
    /// <summary>
    /// DB context.
    /// </summary>
    private IDBManager _dbManager;

    /// <summary>
    /// Repository provider class which can create repositories on demand.
    /// </summary>
    private IRepositoryProvider _repositoryProvider;

    private TransactionScope _transaction;

    public UnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider)
    {
        _dbManager = dbManager;
        _repositoryProvider = repoProvider;
    }

    public T GetRepository<T>()
    {
        if (_transaction == null)
            _transaction = new TransactionScope();

        return _repositoryProvider.Create<T>(_dbManager);
    }

    public void Save()
    {
        _transaction.Complete();
    }

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

在这种情况下,TransactionScope 将在我创建我的第一个存储库时开始,然后我可以在我的 Controller 中调用保存,如下所示:

    public TicketPost AddTicketPost(int tid, TicketPost update)
    {
        TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update);
        Uow.Save();

        return post;
    }

但是,这意味着将为任何操作创建 TransactionScope - 选择/更新/删除,并且它将从创建第一个存储库的那一刻(即使我可能不需要它)一直持续到交易发生的那一刻要么处置要么完成。

另一种解决方案是使用 DBManager 的事务并从 Controller 调用 BeginTransaction,并在我需要时调用 Commit 或 Rollback。像这样:

Uow.BeginTransaction();
try
{
    TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update);
}
catch (Exception e)
{
    Uow.RollbacTransaction();
}
Uow.CommitTransaction();

但我不太喜欢这种方法。在第一种情况下,我需要捕获异常,这些异常会冒泡到我的 ExceptionsHandler,它会创建对客户端的响应消息。另外,我认为 Controller 是一个中间人,它收到请求并说“嘿,存储库,这是数据,我已经检查过了,做你的事然后给我回电话。”。当它收到来自存储库的“调用”时,它可能会做一些与数据无关的事情,比如发送电子邮件。我喜欢当 Controller 不需要在同一个存储库中一个接一个地调用方法并考虑它需要做的事情来完成工作时,例如:

  1. 更新工单
  2. 设置状态
  3. 用票做点别的事
  4. 发送电子邮件

取而代之的是, Controller 要求存储库处理票证更新并等待它可以发送电子邮件:

  1. 告诉控制者做任何他需要做的事来更新工单。
  2. 等待并发送电子邮件。

我处理 Controller 和存储库的方式可能是错误的。如果我错了,请纠正我。

我希望有人能给我指点资源,或者可能有人具有类似的设置并且已经找到了针对这种情况(交易问题)的解决方案。

如有任何帮助,我们将不胜感激。非常感谢您。

最佳答案

如果我没理解错的话,您想要一种方法来避免创建不需要的事务?

为此,我建议您创建一个 BaseUnitOfWork 类型和两个其他类型:ReadOnlyUnitOfWork 和 ReadWriteUnitOfWork。

然后,在选择内容时您将只使用 ReadOnly,而在您需要两者时使用 ReadWrite。

C# 中的框架类似于。

public class BaseUnitOfWork // YOUR INTERFACES HERE
{
    /// <summary>
    /// DB context.
    /// </summary>
    private IDBManager _dbManager;

    /// <summary>
    /// Repository provider class which can create repositories on demand.
    /// </summary>
    private IRepositoryProvider _repositoryProvider;

    public BaseUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider)
    {
        _dbManager = dbManager;
        _repositoryProvider = repoProvider;
    }

    public T GetRepository<T>()
    {
        return _repositoryProvider.Create<T>(_dbManager);
    }
}

public class ReadOnlyUnitOfWork : BaseUnitOfWork
{
    public ReadOnlyUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) : base(dbManager,repoProvider)
    {
        _dbManager = dbManager;
        _repositoryProvider = repoProvider;
    }
}

public class ReadWriteUnitOfWork : BaseUnitOfWork// YOUR INTERFACES HERE
{
    private TransactionScope _transaction;

    public ReadWriteUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) : base(dbManager,repoProvider)
    {
        if (_transaction == null)
            _transaction = new TransactionScope();
    }

    public void Save()
    {
        _transaction.Complete();
    }

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

我已经在几个项目中成功地使用了这个策略。

此策略的好处在于您遵守 SOLID (http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) 设计中的 S:单一职责。

一个类负责处理带有事务的数据库操作,而另一个只处理无事务操作。

此外,您必须了解工作 block 单元应该快速(执行时)并且(如果可能)代码量小(作为最佳实践)。

因此,您可以像这样使用它:

using( IReadWriteUnitOfWork uow = InjectionFramework.ResolveDependencyOfType<IReadWriteUnitOfWork>() )
{
    //do your database stuff here, try to keep it simple.
    //after doing everything, you **commit** the transaction (in your case, you save)
    uow.Save();
}

使用命令的好处是,在您完成这段代码后,它会自动调用您的 dispose 方法。

关于没有带数据库事务或 TransactionScope 的 EF 的 C# UnitOWork 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21480889/

相关文章:

php - Yii2 事务 - 更新先前未提交的值加上新的提交值?

c# - 如何构建 xml 部分并保存到文件(本身不是有效的 xml 文档)

c# - 在另一个文件中使用一个文件的方法

c# - 使用虚假数据进行 wcf 服务测试

c# - 更新 Entity Framework 模型后,Visual Studio 看不到更改

java - 遗留代码的集成测试中如何处理事务

c# - Unity - WWW.text 在 Android 设备上返回 null

c# - 如何在不使用 HttpContext 静态类的情况下获取 ApiController 中的 HttpRequest 对象?

c# - 使用 EntityDataSource 过滤数据

java - 当交易超时时如何执行一些操作