0

我正在使用 Asp.net MVC 4、NHibernate 和 Session-per-request 进行开发。

我有一个更新多个数据库的服务方法,因此工作包含在 TransactionScope 中。我发现 NHibernate Session 在 TransactionScope 之外不可用,因为它不是线程安全的。

代码与此类似:

public void ProcessItems()
{
  var items = itemService.GetAll();
  var mailMessages = new List<MailMessage>();
  using(var scope = new TransactionScope())
  {
    foreach(var item in items)
    {
      itemService.UpdateOne(item);
      itemService.UpdateTwo(item);
      try 
      {
        mailMessages.Add(itemService.GenerateMailMessage(item));
      }
      catch(Exception ex)
      {
        // we don't want exceptions caused be generating email to prevent DB work
        if (ex is InvalidOperationException
          || ex is NullReferenceException
          || ex is FormatException
          || ex is ArgumentException
          || ex is ItemNotFoundException)
        {
          LogError(String.Format("Unable to generate email alert for item.Id:{0} - {1}", item.Id, ex.Message), log);                                
        }
        else
        {
          // For exception types we don't know we can ignore rethrow
          throw;
        }
    }
    scope.Complete()
  }
  mailService.SendMail(mailMessages);
}

数据库更新对于该方法的成功至关重要。电子邮件警报不是。我不希望在生成电子邮件警报时出现问题,以防止发生数据库更新。

我的问题是:

  1. 考虑到这些限制,这看起来是一种合理的方法吗?
  2. 我担心在生成电子邮件时可能会抛出我尚未处理的异常。这将导致整个 TransactionScope 被回滚。感觉就像我希望在该 try 代码块中发生的任何异常都被忽略。但是,我很欣赏一个包罗万象的做法是一个禁忌,因此欢迎任何其他使这个更强大的建议。

编辑

只是为了澄清我的问题:

我知道在 TransactionScope 之后生成和发送电子邮件会更好。但是我无法做到这一点,因为 GenerateMailMessage() 使用了 NHibernate Session,在 TransactionScope 块之外使用它是不安全的。

我想我真正要问的是,为了为关键的 UpdateOne() 和 UpdateTwo() 调用提供与可能的?

4

2 回答 2

3

更新

我的建议是尽量防止发生异常。如果做不到这一点,包罗万象可能是您剩下的唯一选择。记录所有异常在这里将是至关重要的。


第一个问题:您的案例并不是真正的包罗万象,您正在捕获所有异常来查询类型。我唯一的建议是记录您选择使用的异常的详细信息。

第二个问题:如果可能会失败,我会完全从范围中删除电子邮件的生成。一旦事务回滚,所有项目也将回滚。创建并发送成功提交的所有电子邮件。

public void ProcessItems()
{
  var items = itemService.GetAll();
  var mailMessages = new List<MailMessage>();
  bool committed = false;

  using(var scope = new TransactionScope())
  {
    foreach(var item in items)
    {
      itemService.UpdateOne(item);
      itemService.UpdateTwo(item);
    }
    scope.Complete()
    committed = true;
  }

  if (committed)
  {
    // Embed creation code and exception handling here.

    mailService.SendMail(mailMessages);
  }
}
于 2013-09-02T13:40:20.887 回答
0

我建议改变这个。而不是在那里生成电子邮件,然后......将成功处理的项目列表保留在本地列表中,然后在您提交后在最后发送所有邮件。

public void ProcessItems()
{
  var items = itemService.GetAll();
  var successItems = new List<Item>();
  var mailMessages = new List<MailMessage>();
  using(var scope = new TransactionScope())
  {
    foreach(var item in items)
    {
      itemService.UpdateOne(item);
      itemService.UpdateTwo(item);
      successItems.Add(item);

// you still need try/catch handling for DB updates that fail... or maybe you want it all to fail.
    }
    scope.Complete()
  }

  mailMessages = successItems.Select(i => itemService.GenerateMailMessage).ToList();

  //Do stuff with mail messages

}
于 2013-09-02T13:44:48.113 回答