我有一个 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 = false
is BAD。除此之外,我不能真正说出为什么会出现问题,但是将其设置为 true 解决了我的问题,并且仍然允许客户端正确提交或回滚事务(我的印象并非如此TransactionAutoComplete = true