views:

692

answers:

2

I have a WCF service which is used to add tenders to the database, which is MS SQL Server 2005. WCF uses LINQ-to-SQL.

Each tender can have a lot of documents and a lot of items. The customers can add one object per service call. That is, the do something like this:

TendersServiceClient service = new TenderServiceClient();
service.BeginTransaction();

// Adding a new tender
service.AddTender(TenderDTO tenderInfo);

// Adding tender's documents
foreach (DocumentDTO documentInfo in documents)
   service.AddTenderDocument(tenderInfo.TenderID, documentInfo);

// Adding tender's items
foreach (ItemDTO itemInfo in items)
   service.AddTenderItem(tenderInfo.TenderID, itemInfo);

service.CommitTransaction();

Notice the BeginTransaction() and CommitTransaction(). That is, all the above procedure must either succeed completely or be rolled back completely. For example, if one of the items couldn't be inserted, then the whole tender shouldn't exist...

So the question is how do I implement this kind of transaction. The problem is that WCF is stateless, of course. So new DataContext is created for each service call. If I use a static DataContext instead, then I'll be able to use its built-in transactions capabilities, but then how can I handle other customers who can try to add another tender in the same time (they must be, of course, outside this transaction)?

So please help me with some kind of design pattern to achieve this. I am free to change the code both of the service and of the client, so feel free with your suggestions =)

+1  A: 

First off, you would have to use transactional service calls here - and since you have a "initializing" call, a bunch of intermediary calls, and then possibly one to end all the calls, I would recommend you have a look at the "IsInitiating" and "IsTerminating" attributes on the OperationContract for the methods - this will allow you to specify one method to start your session, and one that will finish it.

Next, make sure to configure your service as a transactional service by putting the "TransactionFlow" attribute on either the service or all operations - whichever you prefer.

In your client code, you'll have to use System.Transactions to create a TransactionScope, which will wrap your service calls. This is a lightweight or a fully two-phased distributed transaction coordinator - depending on what your calls do in detail.

Something along those lines:

1) Mark your binding as transactional:

<bindings>
  <wsHttpBinding>
    <binding name="TransactionalWsHttp" transactionFlow="true" />
  </wsHttpBinding>
</bindings>

2) Service contract:

[ServiceContract]
public interface ITenderService
{
  // method to start your submission process
  [OperationContract(IsInitiating=true, IsTerminating=false)]
  [TransactionFlow(TransactionFlowOption.Mandatory]
  public void StartTenderProcess();

  // all your other methods "in between"
  [OperationContract(IsInitiating=false, IsTerminating=false)]
  [TransactionFlow(TransactionFlowOption.Mandatory]
  public void AddTender()

  [OperationContract(IsInitiating=false, IsTerminating=false)]
  [TransactionFlow(TransactionFlowOption.Mandatory]
  public void AddTenderDocument()

  [OperationContract(IsInitiating=false, IsTerminating=false)]
  [TransactionFlow(TransactionFlowOption.Mandatory]
  public void AddTenderItem()

  ...

  // method to end your submission process
  [OperationContract(IsInitiating=false, IsTerminating=true)]
  [TransactionFlow(TransactionFlowOption.Mandatory]
  public void FinishTenderProcess();
}

3) In your client code:

using (TransactionScope ts = new TransactionScope())
{
   serviceClient.StartTenderProcess();

   ..... 

   serviceClient.FinishTenderProcess();

   ts.Complete();   // Transaction Commit 
}

Does that help you for the time being to get you started ??

Marc

marc_s
Hmm...IsInitiating and IsTerminating attributes seem to be really what I need. But there is a problem with TransactionScope on the client side: some of my clients are C++-unmanaged. Should I implement the whole thing on the server side?
Dmitry Perets
And also can you please point out how this thing connects to LINQ-to-SQL? Should I create a separate DataContext for each WCF transaction or there is a way to link between WCF transaction and LINQ-to-SQL transaction?
Dmitry Perets
Hi Dmitry - ok, C++ unmanaged.... that's going to be tough....
marc_s
Well, if you're using Session mode to associate a whole session (from Start.... to Finish) with a single service instance on the server side, you could try to "cache" the DataContext between calls. Never done that myself, though...
marc_s
I am using basicHttpBinding for legacy, and now I understand that this binding supports almost nothing =) People say that I can use ASP.NET session somehow (I am already using asp.net compatibility mode anyway), but I can't make it work so far - every call has new session ID (well, in basicHttpBinding the only supported session mode is PerCall, so this makes sense).
Dmitry Perets
Anyway, I think that I'll do something else in my case. I've configured my db to perform cascade deletes, so if I remove a tender, it automatically deletes all the related stuff (items, documents etc.) So in case of any failure I can just remove the tender in one call and leave to SQL Server to do all the rest... Sounds like an easy and rather good approach, what do you think?
Dmitry Perets
Yes, if you can solve your problem with cascading deletes on the database level - absolutely use it!
marc_s
Well, this solves only one part of the problem. Because if my connection to db fails, then I won't be able to even ask it to remove the tender. Perhaps I'll have to implement some custom session in WCF somehow
Dmitry Perets
Personally I would set up two separate endpoints one for the managed clients and one for the C++ clients. That way when the C++ clients are updated they will automatically get transaction support.
Jonathan Parker
Yes, good point, Jonathan. That would work, too, and probably ever better than having just one endpoint.
marc_s
+2  A: 

Do you control the interface of the service?

If so, surely the elegant solution is for the service to accept an aggregate Tender object in a single method rather than having the chatty methods that you have now. The Tender would then have Items and Documents as subcollections, and the data access code could handle all the updates in single transaction much more easily.

Unless I am misunderstanding, it seems very similar to an Order/OrderDetails scenario, where the same logic applies.

grundoon
I agree. The proposed code is very RPC-like. Much better off chunking things up and letting the service handle the transaction.
Brook