Reputation: 1113
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 =)
Upvotes: 1
Views: 1460
Reputation: 235
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.
Upvotes: 2
Reputation: 754468
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
Upvotes: 1