如何在使用CoreService的自定义类中实现WCF事务支持?

8
我写了一个类来帮助使用Core Service添加和删除发布目标。通过Core Service,通常将发布目标暴露为一个字符串(包含XML内容),因此我编写了自己的包装器等。
现在我遇到一个情况,需要更新两个发布目标,并想使用事务范围来确保同时更新这两个目标。但是,我在实现这一点时遇到了困难。
以下是可行的代码(使用标准的CoreService WCF客户端):
TransactionOptions txOptions = new TransactionOptions 
                    { IsolationLevel = IsolationLevel.ReadCommitted };
using(TransactionScope scope = new TransactionScope(
                            TransactionScopeOption.Required, txOptions))
{
    PublicationTargetData publicationTarget1 = (PublicationTargetData)client.Read("tcm:0-1-65537", readOptions);
    PublicationTargetData publicationTarget2 = (PublicationTargetData)client.Read("tcm:0-2-65537", readOptions);

    publicationTarget1.TargetLanguage = "JSP";
    publicationTarget2.TargetLanguage = "JSP";
    client.Save(publicationTarget1, readOptions);
    client.Save(publicationTarget2, readOptions);

    // Stop saving
    scope.Dispose();
}

执行此代码将成功撤消我所做的更改(如果在scope.Dispose()之前中断并在Tridion中检查发布目标,则会成功更改目标,然后“撤消”更改)。
现在,如果我尝试在事务中使用我的“扩展发布目标”类,我无法处理它。
TransactionOptions options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options))
{
    ExtendedPublicationTarget target1 = new ExtendedPublicationTarget("tcm:0-1-65537");
    ExtendedPublicationTarget target2 = new ExtendedPublicationTarget("tcm:0-2-65537");
    target1.Destinations.Add(target1.Destinations[0]);
    target2.Destinations.Add(target2.Destinations[0]);
    target1.Save();
    target2.Save();
    scope.Dispose();
}

基本上,这是问题:我应该怎么做才能在我的 .Save() 方法中添加事务处理?

我尝试过以下方法:

[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
public void Save()
{
    _client.Save(targetData, readOptions);
}

但这并没有什么区别。有没有办法确定我当前是否处于事务中,并以某种方式“使用”该事务?我不想要求一个事务,只是想有在其中操作的选项。

谢谢,很抱歉发了这么长的帖子……只是想提供尽可能多的信息。


由于您在using子句中有scope变量,因此不应显式调用Dispose。相反,当using块结束时,它将自动被处理。 - Frank van Puffelen
你说“我无法处理它”。这是什么意思?你得到了编译器错误吗?异常?还是代码只是没有更新你的目标?(附言:如果 WCF 事务按照我所习惯的命名,TransactionScope 将要么“搭便车”在现有事务上,要么启动一个新事务。但由于我没有关于 WCF 事务的经验,我更愿意看到其他人确认/更正这个假设)。 - Frank van Puffelen
嘿,弗兰克,我正在尝试让它失败,因此我调用了scope.Dispose()。在第一个示例中,它正确地失败了,在第二个示例中则没有。我只是想弄清楚我需要添加什么到我的类中才能使它工作... - Nuno Linhares
1个回答

3
这方面的最佳资源是:WCF事务传播
您还缺少至少一步。 您还需要在绑定中启用事务:
<bindings>
   <netTcpBinding>
      <binding name = “TransactionalTCP” transactionFlow = “true” />
   </netTcpBinding>
</bindings>

有没有办法确定我当前是否在一个事务中,并且以某种方式“使用”该事务?
是的。要确定您是否处于事务中,可以检查Transaction.Current。如果您正在事务中,则会使用它,除非您明确选择退出。这就是环境事务的美好/可怕之处。
参见《WCF事务传播》中的图5:http://msdn.microsoft.com/en-us/magazine/cc163432.aspx
class MyService : IMyContract 
{
   [OperationBehavior(TransactionScopeRequired = true)]   
   public void MyMethod(...)
   {
      Transaction transaction = Transaction.Current;
      Debug.Assert(transaction.TransactionInformation.
                   DistributedIdentifier != Guid.Empty);
   } 
}

如果Transaction.Current.TransactionInformation.DistributedIdentifier为空,则事务是本地的,没有“流动”。请注意,在TransactionFlowOptions.Allowed配置中,如果事务无法流动,则会静默失败。因此,这确实是唯一的检查方式……而不发生流动比您预期的更容易。
当我为生产服务使用事务时,我实际上避免了TransactionFlowOptions.Allowed,因为调用者从未确定事务是否实际流动。如果在部署中存在绑定配置错误,则一切都会正常运行,但回滚将失败……这是一个非常繁琐的错误。所以我切换到了required。然后,调用者可以确保他们提供的事务已经成功传递。(如果在TransactionFlowOptions.Required配置中事务不流动,您将收到异常。)

太棒了。我已经通过编程方式创建了NetTCP绑定,但没有设置binding.TransactionFlow = true - 在这样做之后,一切都按预期工作。非常感谢。 - Nuno Linhares

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接