2

我有一个 WCF 服务,我正在尝试使其具有事务性。当我使用事务调用服务时,事务没有被使用,如果我回滚事务,我的数据库更新无论如何都会发生。

这是服务接口(我只包括我一直在测试的单一方法:

[ServiceContract(SessionMode=SessionMode.Required)]
public interface IUserMenuPermissionService 
{
    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    [TransactionFlow(TransactionFlowOption.Allowed)]
    void InsertUserMenuPermission(string userId, string menuId, int permission);
}

服务实现是:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class UserMenuPermissionService : IUserMenuPermissionService
{
    private static IUserMenuPermissionProvider _provider = new CoreDataFactory().GetUserMenuPermissionProvider();
    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
    public void InsertUserMenuPermission(string userId, string menuId, int permission)
    {
        _provider.InsertUserMenuPermission(userId, menuId, permission);
    }
}

实际的底层提供者并没有直接对交易做任何事情,但在这一点上,这不是我的问题,稍后会变得很清楚。

WCF 的 app.config 具有:

  <bindings>
      <wsHttpBinding>
          <binding name="TransactionBinding" transactionFlow="True" >
          </binding>
      </wsHttpBinding>
  </bindings>

  ...
  ...

  <service name="GEMS.Core.WCFService.UserMenuPermissionService">
    <endpoint address=""  bindingConfiguration="TransactionBinding" binding="wsHttpBinding" contract="SvcProvider.Core.WCFService.IUserMenuPermissionService">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    <host>
      <baseAddresses>
        <add baseAddress="http://localhost/SvcProvider.WebService/SvcProvider.Core.WCFService/UserMenuPermissionService/" />
      </baseAddresses>
    </host>
  </service>

客户端是一个 ASP.NET MVC Web 应用程序。它的网络配置有:

        <wsHttpBinding>
            <binding name="TransactionBinding" transactionFlow="true" />
        </wsHttpBinding>

        ....
        ....

        <endpoint address="http://localhost/GEMS.WebService/SvcProvider.Core.WCFService.UserMenuPermissionService.svc"
            binding="wsHttpBinding" bindingConfiguration="TransactionBinding"
            contract="UserMenuPermissionService.IUserMenuPermissionService"
            name="WsHttpBinding_IUserMenuPermissionService" />

当我从客户端调用 Web 服务时,我是在事务范围内进行的。如果我看一下System.Transactions.Transaction.Current,它被设置为一个有效的 System.Transactions.Transaction 并且它有一个 DistributedIdentifier 集。

但是,当我在 WCF 服务中时,System.Transactions.Transaction.Current 设置为 null。

所以看来我的交易没有传递给服务。我希望交易是可选的,但是当有交易时,显然,我希望它被使用。

我错过了一步吗?

更新

根据 BNL 的评论,我现在设置了 TransactionScopeRequired = true (更新了上面的代码以反映这一点)。

如果我在客户端调用没有事务范围的方法,它们似乎运行得很好(我假设服务创建了一个事务)。

如果我在客户端上创建一个事务范围并尝试调用该方法,当我第一次尝试调用它时,它会挂起大约 55 秒,然后我得到一个事务中止异常。我将事务的超时时间设置为 3 分钟(因为默认值为 1 分钟),但这在大约 55 秒时继续发生。延迟似乎在客户端,因为在实际调用 WCF 服务之前调用自动生成的客户端代理方法需要 55 秒。随后的呼叫没有 55 秒的延迟。代码如下:

using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(0, 3, 0)))
{
    _userMenuPermissionManager.SetPermissions(clientCode, menuPermissions);
    ts.Complete();
}

异常发生在处置中,而不是 ts.Complete() 调用中。

at System.Transactions.TransactionStatePromotedAborted.PromotedTransactionOutcome(InternalTransaction tx)
at System.Transactions.TransactionStatePromotedEnded.EndCommit(InternalTransaction tx)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
at WebApp.Controllers.AdminController.SetUserMenuPermissions(String clientCode, MenuPermission[] permissions) in Controllers\\AdminController.cs:line 160

正在调用该服务并将事务传递给它。根据服务跟踪日志,没有错误。一切似乎都很顺利,但最后,它收到来自客户端的中止,我猜想回滚事务。

客户端服务日志显示也没有任何异常可以解释 55 秒的延迟。

因此,虽然我走得更远(感谢 BNL),但我仍然不完全在那里。

更新 2

我补充说:

[ServiceBehavior(TransactionTimeout = "00:03:00", InstanceContextMode = InstanceContextMode.PerSession)]

到服务实现,55 秒的延迟变成了 2 分 55 秒的延迟,所以看起来事务正在超时,然后服务方法被调用(使用事务),服务完成所有工作,然后客户端在最后发送一个中止。TransactionScopes单独中止下的后续调用立即...

更新 3

看来超时是由我错过的早期调用引起的,该调用没有在显式事务中发生,并且因为我没有自动提交事务,所以它挂起是因为有一个未提交的事务。我现在将所有电话都包含在交易中。第一个呼叫现在立即中止。但在服务或客户端跟踪日志中仍然没有任何问题的迹象。没有内在的例外,什么都没有。只是流产...

更新 4

除了 BNL 的答案,显然 using TransactionAutoComplete = falseis BAD。除此之外,我不能真正说出为什么会出现问题,但是将其设置为 true 解决了我的问题,并且仍然允许客户端正确提交或回滚事务(我的印象并非如此TransactionAutoComplete = true

4

1 回答 1

2

在您的第二个代码块中,您有TransactionScopeRequired = false.

如果您查看本文档的备注部分,您可以确定您不会进行交易。

http://msdn.microsoft.com/en-us/library/system.servicemodel.operationbehaviorattribute.transactionscoperequired.aspx

TransactionScopeRequired = false

绑定许可交易流 = true

调用者流事务 = true

结果 = 方法在没有事务的情况下执行。

奇怪的是,如果前两列都为真但客户端没有处理事务,会发生什么情况。看起来该服务将创建一个新事务,但我不能 100% 确定这一点。

于 2013-02-12T18:30:53.727 回答