在工作单元中使用数据库操作和外部方法

时间:2018-04-13 21:15:30

标签: c# design-patterns unit-of-work asp.net-boilerplate

关于Unit Of Work Pattern

  

工作单元设计模式做了两件重要的事情:首先它   维护内存中更新,然后在内存中发送这些内容   作为一个事务更新到数据库

假设我需要使用Web服务并在同一工作单元中更新数据库表。按照惯例,我执行以下步骤:

  1. 打开连接。
  2. 调用DB Insert方法(如果执行该方法则没有风险,但在UOW中当时没有FLUSHED。)
  3. 致电外部网络服务(来自其他公司的任何服务)。
  4. 如果Web服务结果为200(OK),则调用commit,否则回滚。

    using (var unitOfWork = _unitOfWorkManager.Begin())
    {
        _personRepository.Insert(person);
        externalWebService.IncrementPeopleCount();
    
        unitOfWork.Complete();
    }
    
  5. unitOfWork有一个方法:Complete()。如果Web服务出错,那就不是问题。因为我可以抛出异常,所以complete()方法不会执行。但是我在完成时遇到错误()我必须反向Web服务?

    如果在DB中执行Insert()方法事务(尚未提交),它将正常工作。

    如何在工作单元模式中执行此方案?或者是否必须采用反向网络方法DecrementPeopleCount

3 个答案:

答案 0 :(得分:1)

Unit of Work中,您管理transactional次操作。在Unit of Work范围内,您运行一些业务逻辑,当所有操作都准备就绪时,将调用CompleteSaveChanges个操作。此操作为您提供了一个机会,您的所有操作都已成功完成或所有操作都已取消。在这种情况下,代码不能作为一个单元。有两个separete操作,插入操作和Web服务增量操作。

DecrementPeopleCount操作不是解决方案。想象一下这样的事情:IncrementPeopleCount操作返回成功,但是完成操作返回错误。在此之后,您尝试调用DecrementPeopleCount,但Web服务太忙或出现网络问题。通过这种方式,您的代码仍然无法作为一个单元运行。

作为解决方案,您考虑改变您的方法。

1)WebService调用操作可以换行并转换为事务操作。我建议使用名为Hangfire的工具。它在db和事务中保存操作名称和参数,完成读取db和触发寄存器功能。您可以将entitycall webservice operation execution保存在db中作为一个操作。

2)您可以保存entity并发布user-created eventincrement-user-count command。您的观察者/消费者使用该事件/命令并执行web api调用。

两种解决方案最终都具有一致性。

答案 1 :(得分:1)

来自Unit Of Work的文档:

  

如果方法抛出异常,则事务回滚,并且连接已被释放。通过这种方式,工作单元方法是原子工作单元)。

  

您无需执行任何操作,但有时您可能希望在工作单元操作过程中保存对数据库的更改。

     

您可以使用当前工作单元的 SaveChanges SaveChangesAsync 方法。

     

请注意,如果当前工作单元是事务性的,则在发生异常时将回滚事务中的所有更改。即使是保存的更改!

因此,在调用Complete进行回滚之前抛出异常:

try
{
    using (var unitOfWork = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
    {
        _personRepository.Insert(person);

        // Save changes
        _unitOfWorkManager.Current.SaveChanges();

        var result = externalWebService.IncrementPeopleCount();
        if (result != 200)
        {
            // Rollback
            throw new MyExternalWebServiceException("Unable to increment people count!");
        }

        // Commit
        unitOfWork.Complete();
    }
}
catch (MyExternalWebServiceException)
{
    // Transaction rolled back, propagate exception?
    throw;
}

MyExternalWebServiceException可以继承UserFriendlyException以向用户显示:

public class MyExternalWebServiceException : UserFriendlyException
{
    public MyExternalWebServiceException(string message)
        : base(message)
    {
    }
}

答案 2 :(得分:0)

externalWebService调用在做什么?它正在同步另一个数据仓库吗?还是更新ui元素?

如果是UI,那么我会把两者分开,因为你的数据完整性不应该关心,更不用说依赖了,ui元素更新成功了。

我可以就同步两个数据集做出类似的论点。

如果更新调用失败,则不应影响源库中的数据完整性。相反,通过编写第二个同步方法来补偿失败,该方法将从人员库中检索实际计数,并在增量调用失败时使用该值更新外部源。事实上,我建议这个更新而不是增量更新。

在这种特殊情况下,我看不出为什么人员回购的数据完整性应该取决于第二次调用是否成功的原因。