Wcf 如何防止事务作用域抛出我已经处理过的异常?

Wcf 如何防止事务作用域抛出我已经处理过的异常?,wcf,transactions,Wcf,Transactions,我有一个WCF操作,概念上是这样的: [OperationBehavior(TransactionScopeRequired = true)] public void Foo() { try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); } catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); } }

我有一个WCF操作,概念上是这样的:

    [OperationBehavior(TransactionScopeRequired = true)]
    public void Foo() 
    {
        try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); }
        catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
    }
[OperationBehavior(TransactionScopeRequired = true)]
public void Foo() 
{
    // Resolve the default ExceptionManager object from the container.
    ExceptionManager exManager = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();

    exManager.Process(() => 
      {
          DAL.Foo(); 
          return Receipt.CreateSuccessReceipt(); 
      }, 
      "ExceptionShielding");
}
如果在执行DAL代码时出现了错误(比如,外键约束违反),控制权会像我预期的那样传递给catch块。但是当方法返回时,事务作用域似乎已经嗅出事务失败,并且它决定最好抛出一个异常,以确保通知调用方该异常

反过来,我的客户端应用程序没有收到我想要返回的收据,而是出现了一个异常:

System.ServiceModel.FaultException:  
  The transaction under which this method call was executing was asynchronously aborted.
我的设计有什么问题

我可以让服务不捕获任何内容,但这有它自己的问题,因为服务需要使用异常屏蔽,客户端(系统内部的批处理工具)需要记录错误信息。该服务也会记录错误,但记录方式和位置与批处理不同

[OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
大概是因为事务现在在错误发生时立即回滚,而不是在作用域超出作用域时异步回滚:D,这样的行为就像我最初预期的那样,我可以保持我的设计原样


(我在尝试这个问题时已经写下了这个问题。希望以问答的方式发布它比根本不发布这个问题更有帮助。)

在这里要小心!如果设置TransactionAutoComplete=true,则如果服务正常返回,则事务将被提交。只有当存在未处理的异常时(在大多数情况下,由于捕获异常并返回回执消息,您没有异常),事务才会回滚。看

考虑一个场景,您成功地执行了一些DAL调用,但发生了一些其他异常(例如NullReferenceException)。现在,当方法完成时,事务将被提交,因为没有发生未处理的异常,但客户端收到一个ErrorReceive

对于您的场景,我认为您必须自己管理事务。例如:

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo() 
{   
    // Create TransactionScope using the ambient transaction
    using (var scope = new TransactionScope() )
    {
        try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); scope.Complete(); }
        catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
    }
}
您可以通过创建一个helper方法来消除样板代码,该方法将所有样板代码封装在事务中,或者您可以使用策略注入/拦截/方面来管理事务

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo() 
{   
    return ProcessWithTransaction(() =>
        {
            DAL.Foo();
            return Receipt.CreateSuccessReceipt();
        }
        , (ex) =>
        {
            return Receipt.CreateErrorReceipt(ex);
        }
    );
}

T ProcessWithTransaction<T>(Func<T> processor, Func<Exception, T> exceptionHandler)
{
    using (var scope = new TransactionScope())
    {
        try
        {
            T returnValue = processor();
            scope.Complete();
            return returnValue;
        }
        catch (Exception e)
        {
            return exceptionHandler(e);
        }
    }
}
[操作行为(TransactionScopeRequired=true,TransactionAutoComplete=false)]
公众收据Foo()
{   
返回ProcessWithTransaction(()=>
{
DAL.Foo();
返回收据。CreateSuccessReceipt();
}
,(ex)=>
{
返回收据。CreateErrorReceipt(ex);
}
);
}
T ProcessWithTransaction(函数处理器、函数例外处理程序)
{
使用(var scope=new TransactionScope())
{
尝试
{
T returnValue=处理器();
scope.Complete();
返回值;
}
捕获(例外e)
{
返回异常处理程序(e);
}
}
}
您提到需要使用异常屏蔽。如果您不反对在错误发生时抛出错误,那么可以使用企业库异常处理块的异常屏蔽,它还允许您在退出时记录信息(如果您愿意)

如果您决定走这条路线,您的代码将如下所示:

    [OperationBehavior(TransactionScopeRequired = true)]
    public void Foo() 
    {
        try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); }
        catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
    }
[OperationBehavior(TransactionScopeRequired = true)]
public void Foo() 
{
    // Resolve the default ExceptionManager object from the container.
    ExceptionManager exManager = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();

    exManager.Process(() => 
      {
          DAL.Foo(); 
          return Receipt.CreateSuccessReceipt(); 
      }, 
      "ExceptionShielding");
}
[操作行为(TransactionScopeRequired=true)]
公共图书馆
{
//从容器中解析默认的ExceptionManager对象。
ExceptionManager exManager=EnterpriseLibraryContainer.Current.GetInstance();
exManager.Process(()=>
{
DAL.Foo();
返回收据。CreateSuccessReceipt();
}, 
“例外屏蔽”);
}

然后,企业库(通过配置)将捕获任何异常,并将其替换为返回到客户端的新FaultException。

。谢谢。不幸的是,我的回答是胡说八道,因为它包含一个险恶的bug,无论我们制造了什么样的混乱,事务总是提交!:我认为您看到的是,该方法正在退出,WCF正在尝试提交该事务,但数据库知道该事务未处于可提交状态,因此该事务被中止。上面来自@RandyLevy的评论为我们解释了该行为。在DAL的堆栈中执行得更深的代码有一个事务作用域,但会引发异常。这导致事务仍然中止并通知WCF,尽管将异常包装为有效的无故障响应。好的-我们需要仔细考虑一下如何处理这个问题。谢谢大家!非常感谢。我只是忽略了由此产生的错误提交问题。现在我只希望创建事务作用域并处理它的行为意味着当作用域未完成时,事务被同步中止(回滚)。顺便说一句,我们使用EntLib屏蔽异常,并记录日志。但我也想在客户端登录。