3
votes

This is my first WCF both client and server side. SQL server 2008.

I'm forced to use TransactionScope transactions from within my windows forms application that's running as a client against a WCF service. I've not used this type of transaction before, usually using connection.BeginTransaction() which I like because it's easier to follow what's going on.

I call a function (that I'm hoping is nesting transactions) but one of my calls to create a new TransactionScope instance falls over with "The transaction has aborted" exception. Inner exception is null. I'm assuming that the transaction has rolled back prior to this, but I've no idea how to track if there was an ambient transaction in progress and what the status of that was.

example client (not real code. I've checked all funcs are using scope.Commit()) Any help on how to track down the problem please?

(often I receive error messages about having to start the MSDTC service, which I do, but I don't understand why it's using distributed when there's a single database/wcf service)

void Function1(AMessageObj m)
{
  using (var client = GetOpenWebClient())
    WriteMessage(m, client);
}

void WriteMessage(AMessage m, OpenWebClient client)
{
  using (var scope = new TransactionScope())
  {
    var audit = GetSomeAuditInfo(AMessage.UserId, client);
    WriteTheMessage(AMessage, client);
    WriteAudit(audit, client);
    client.SetTime(DateTime.Now);
    scope.Commit();
  }  
}

AuditRecord GetSomeAuditInfo(Int userId, OpenWebClient client)
{
  var result = client.ReadAuditRecord(userId);
  return AuditRecord.FromWeb(result);
}

void WriteTheMessage(AMessage msg, OpenWebClient client)
{
  using (var scope = new TransactionScope())
  {
    client.WriteTheMessage(msg.ToWeb());
    scope.Commit();
  }
}

void WriteAudit(AuditRecord audit, OpenWebClient client)
{
  using (var scope = new TransactionScope())
  {
    client.WriteAudit(audit.ToWeb());
    scope.Commit();
  }
}

example service WCF

[ServiceContract]
interface IService
{
  [OperationContract]
  void WriteTheMessage(WebRecord message);

  [OperationContract]
  WebRecord ReadAuditRecord(int userId);

  [OperationContract]
  void WriteAudit(WebRecord audit);
}

class MyWebService : IService
{
  [OperationBehavior(TransactionScopeRequired = true)]
  void IService.WriteTheMessage(WebRecord record)
  {
    using (var connection = GetOpenConnection())
    {
      dowritethings;
    }
  }

  [OperationBehavior(TransactionScopeRequired = false)]
  WebRecord IService.ReadAuditRecord(WebRecord record)
  {
    using (var connection = GetOpenConnection())
    {
      return readthings;
    }
  }

  [OperationBehavior(TransactionScopeRequired = true)]
  void IService.WriteAudit(WebRecord record)
  {
    using (var connection = GetOpenConnection())
    {
      dowritethings;
    }
  }
}
2
Inner exception is Null, Any of the object inside your code is Null at runtime, which in turn null reference exception and will automatically abort transaction. So you have to find out which object is nullVimal CK
Are you able to deterministically repeat the issue or is it an intermittent and race-condition based?Eugene Podskal

2 Answers

3
votes

I did a web search on How to view ambient transaction status and came up with Transaction.Current property which I put into the watch window. This showed Aborted after one external function call I'd missed completely. Stepping into that I found that I called return; from the function between creating the transaction and committing it hence the abort.

Thanks for your help

1
votes

Each connection you open on the server is a distributed entity. By chance, you often get the same physical connection from the connection pool during tests. This is such a nasty behavior because it leads you to not find the problem during testing. This setup behaves in a non-deterministic way regarding the database.

In any case you should have distributed transactions here because WCF and SQL Server are different resources. This should deterministically escalate to use MSDTC.

I'm assuming that the transaction has rolled back prior to this

Most likely. With WCF transactions your code always has an ambient transaction set up. Probably, there was some exception somewhere that you did not notice. Set the debugger to break on all exceptions. Use Fiddler to observe what responses are sent over the wire. Maybe they contain some kind of error. WCF tracing is another option.

If that does not lead to anything try to create a reliable repro. Often you find the error yourself in the process.