Issue in Transaction Scope inside WCF - wcf

In my Project, DAL Is WCF service .Net4.0. using database oracle 11g. I am using transaction scope in WCF(server side).
I have to call more than one Stored procedure inside the method(operation contract) if any one sp failed, I need to rollback already executed sp. But rollback not happened. I am not used client side transaction flow.
I have placed sample code
public class Service : IService
{
public bool Method1()
{
using (TransactionScope Scope1 = new TransactionScope())
{
Method2();
Method3();
Scope1.Complete();
}
return true;
}
public bool Method2()
{
using (TransactionScope Scope2 = new TransactionScope())
{
// Procedure call .....
Scope2.Complete();
}
return true;
}
public bool Method3()
{
using (TransactionScope Scope3 = new TransactionScope())
{
// Procedure call .....
Scope3.Complete();
}
return true;
}
}

I don't see any reason why that wouldn't work as expected, the way you have your code laid out there.
However if you are using WCF, you might want to consider using the transaction flow stuff built in to WCF. You can wrap the entire WCF call into a single transaction that way, without manually creating and managing TransactionScopes. The WCF transaction flow can be set up to require a transaction at the service side, so WCF will start a transaction for you if the client doesn't pass one. This way you wouldn't have to edit your client at all.
http://msdn.microsoft.com/en-us/library/ms751413.aspx

Related

Wrong Thread.CurrentPrincipal in async WCF end-method

I have a WCF service which has its Thread.CurrentPrincipal set in the ServiceConfiguration.ClaimsAuthorizationManager.
When I implement the service asynchronously like this:
public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
// Audit log call (uses Thread.CurrentPrincipal)
var task = Task<int>.Factory.StartNew(this.WorkerFunction, state);
return task.ContinueWith(res => callback(task));
}
public string EndMethod1(IAsyncResult ar)
{
// Audit log result (uses Thread.CurrentPrincipal)
return ar.AsyncState as string;
}
private int WorkerFunction(object state)
{
// perform work
}
I find that the Thread.CurrentPrincipal is set to the correct ClaimsPrincipal in the Begin-method and also in the WorkerFunction, but in the End-method it's set to a GenericPrincipal.
I know I can enable ASP.NET compatibility for the service and use HttpContext.Current.User which has the correct principal in all methods, but I'd rather not do this.
Is there a way to force the Thread.CurrentPrincipal to the correct ClaimsPrincipal without turning on ASP.NET compatibility?
Starting with a summary of WCF extension points, you'll see the one that is expressly designed to solve your problem. It is called a CallContextInitializer. Take a look at this article which gives CallContextInitializer sample code.
If you make an ICallContextInitializer extension, you will be given control over both the BeginXXX thread context AND the EndXXX thread context. You are saying that the ClaimsAuthorizationManager has correctly established the user principal in your BeginXXX(...) method. In that case, you then make for yourself a custom ICallContextInitializer which either assigns or records the CurrentPrincipal, depending on whether it is handling your BeginXXX() or your EndXXX(). Something like:
public object BeforeInvoke(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.IClientChannel channel, System.ServiceModel.Channels.Message request){
object principal = null;
if (request.Properties.TryGetValue("userPrincipal", out principal))
{
//If we got here, it means we're about to call the EndXXX(...) method.
Thread.CurrentPrincipal = (IPrincipal)principal;
}
else
{
//If we got here, it means we're about to call the BeginXXX(...) method.
request.Properties["userPrincipal"] = Thread.CurrentPrincipal;
}
...
}
To clarify further, consider two cases. Suppose you implemented both an ICallContextInitializer and an IParameterInspector. Suppose that these hooks are expected to execute with a synchronous WCF service and with an async WCF service (which is your special case).
Below are the sequence of events and the explanation of what is happening:
Synchronous Case
ICallContextInitializer.BeforeInvoke();
IParemeterInspector.BeforeCall();
//...service executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();
Nothing surprising in the above code. But now look below at what happens with asynchronous service operations...
Asynchronous Case
ICallContextInitializer.BeforeInvoke(); //TryGetValue() fails, so this records the UserPrincipal.
IParameterInspector.BeforeCall();
//...Your BeginXXX() routine now executes...
ICallContextInitializer.AfterInvoke();
//...Now your Task async code executes (or finishes executing)...
ICallContextInitializercut.BeforeInvoke(); //TryGetValue succeeds, so this assigns the UserPrincipal.
//...Your EndXXX() routine now executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();
As you can see, the CallContextInitializer ensures you have opportunity to initialize values such as your CurrentPrincipal just before the EndXXX() routine runs. It therefore doesn't matter that the EndXXX() routine assuredly is executing on a different thread than did the BeginXXX() routine. And yes, the System.ServiceModel.Channels.Message object which is storing your user principal between Begin/End methods, is preserved and properly transmitted by WCF even though the thread changed.
Overall, this approach allows your EndXXX(IAsyncresult) to execute with the correct IPrincipal, without having to explicitly re-establish the CurrentPrincipal in the EndXXX() routine. And as with any WCF behavior, you can decide if this applies to individual operations, all operations on a contract, or all operations on an endpoint.
Not really the answer to my question, but an alternate approach of implementing the WCF service (in .NET 4.5) that does not exhibit the same issues with Thread.CurrentPrincipal.
public async Task<string> Method1()
{
// Audit log call (uses Thread.CurrentPrincipal)
try
{
return await Task.Factory.StartNew(() => this.WorkerFunction());
}
finally
{
// Audit log result (uses Thread.CurrentPrincipal)
}
}
private string WorkerFunction()
{
// perform work
return string.Empty;
}
The valid approach to this is to create an extension:
public class SLOperationContext : IExtension<OperationContext>
{
private readonly IDictionary<string, object> items;
private static ReaderWriterLockSlim _instanceLock = new ReaderWriterLockSlim();
private SLOperationContext()
{
items = new Dictionary<string, object>();
}
public IDictionary<string, object> Items
{
get { return items; }
}
public static SLOperationContext Current
{
get
{
SLOperationContext context = OperationContext.Current.Extensions.Find<SLOperationContext>();
if (context == null)
{
_instanceLock.EnterWriteLock();
context = new SLOperationContext();
OperationContext.Current.Extensions.Add(context);
_instanceLock.ExitWriteLock();
}
return context;
}
}
public void Attach(OperationContext owner) { }
public void Detach(OperationContext owner) { }
}
Now this extension is used as a container for objects that you want to persist between thread switching as OperationContext.Current will remain the same.
Now you can use this in BeginMethod1 to save current user:
SLOperationContext.Current.Items["Principal"] = OperationContext.Current.ClaimsPrincipal;
And then in EndMethod1 you can get the user by typing:
ClaimsPrincipal principal = SLOperationContext.Current.Items["Principal"];
EDIT (Another approach):
public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
var task = Task.Factory.StartNew(this.WorkerFunction, state);
var ec = ExecutionContext.Capture();
return task.ContinueWith(res =>
ExecutionContext.Run(ec, (_) => callback(task), null));
}

When calling a WCF RIA Service method using Invoke, does the return type affect when the Completed callback is executed?

I inherited a Silverlight 5 application. On the server side, it has a DomainContext (service) with a method marked as
[Invoke]
public void DoIt
{
do stuff for 10 seconds here
}
On the client side, it has a ViewModel method containing this:
var q = Context.DoIt(0);
var x=1; var y=2;
q.Completed += (a,b) => DoMore(x,y);
My 2 questions are
1) has DoIt already been activated by the time I attach q.Completed, and
2) does the return type (void) enter into the timing at all?
Now, I know there's another way to call DoIt, namely:
var q = Context.DoIt(0,myCallback);
This leads me to think the two ways of making the call are mutually exclusive.
Although DoIt() is executed on a remote computer, it is best to attach Completed event handler immediately. Otherwise, when the process completes, you might miss out on the callback.
You are correct. The two ways of calling DoIt are mutually exclusive.
If you have complicated logic, you may want to consider using the Bcl Async library. See this blog post.
Using async, your code will look like this:
// Note: you will need the OperationExtensions helper
public async void CallDoItAndDosomething()
{
this.BusyIndicator.IsBusy = true;
await context.DoIt(0).AsTask();
this.BusyIndicator.IsBusy = false;
}
public static class OperationExtensions
{
public static Task<T> AsTask<T>(this T operation)
where T : OperationBase
{
TaskCompletionSource<T> tcs =
new TaskCompletionSource<T>(operation.UserState);
operation.Completed += (sender, e) =>
{
if (operation.HasError && !operation.IsErrorHandled)
{
tcs.TrySetException(operation.Error);
operation.MarkErrorAsHandled();
}
else if (operation.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(operation);
}
};
return tcs.Task;
}
}

How to use BeginExecuteReader with an async WCF method

I am trying to use BeginExecuteReader method of ADO.Net in an asyn WCF method, but not able to get it.
I have the following contract and service code. I cannot understand how do I fill in the details for callback method in the begin method of service. Any help would be greatly appreciated since I cannot find any examples on the web or any documentation on MSDN for this. Even some link to sample code would help since I am TOTALLY confused with how to do this.
Contract code:
[ServiceContract(Namespace = ServiceConstants.ServiceContractNamespace,
Name = ServiceConstants.ServiceName)]
public interface IAsyncOrderService
{
[OperationContract(AsyncPattern=true)]
IAsyncResult BeginGetProducts(string vendorId, AsyncCallback callback,
object state);
List<Product> EndGetProducts(IAsyncResult result);
}
The service code is:
public IAsyncResult BeginGetProducts(string vendorId, AsyncCallback cb, object s)
{
DocumentsSummaryByProgram summary = null;
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Conn1"].ConnectionString);
SqlCommand sqlCmd = null;
sqlCmd = new SqlCommand("dbo.GetProducts", conn);
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.Parameters.AddWithValue("#vendorId", sqlCmd);
conn.Open();
return sqlCmd.BeginExecuteReader(cb, vendorId);
}
public List<Product> EndGetProducts(IAsyncResult r)
{
List<Product> products = new List<Product>();
SqlCommand cmd = r.AsyncState as SqlCommand;
if (cmd != null)
{
SqlDataReader dr = cmd.EndExecuteReader(r);
while (dr.Read())
{
//do your processing here and populate products collection object
}
}
return products;
}
UPDATE 1 : This seems like an impossible task. Microsoft should have provided examples to show how ADO.Net async methods are called from WCF in async manner, since this would be useful for many apps out there that want to be scalable.
UPDATE 2: I have provided a detailed answer to my question, after I was able to successfully implement async pattern in WCF. Please look at the answer in a separate post below.
You never called opened your SqlConnection
conn.Open();
Also you created two SqlConnection objects:
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Conn1"].ConnectionString);
and:
sqlCmd = new SqlCommand("dbo.GetProducts", new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["VHA_EDM"].ConnectionString));
Edit
To add an async callback you would do:
var callback = new AsyncCallback(HandleCallback);
sqlCmd.BeginExecuteReader(callback, command);
If you don't have any asynchronous code that you planned on running in between BeginExecuteReader and EndExecuteReader you are better off just using ExecuteReader.
Edit 2
The AsyncCallback delegate has the following signature:
public delegate void AsyncCallback(IAsyncResult ar);
From within that delegate method you can Invoke your EndGetProducts method.
Edit 3
Here is an example of retrieving data using BeginExecuteReader:
public SqlCommand Command { get; set; }
public IAsyncResult BeginGetStuff()
{
var connect = "[enter your connection string here]";
// Note: Your connection string will need to contain:
// Asynchronous Processing=true;
var cn = new SqlConnection(connect);
cn.Open();
var cmd = new SqlCommand("[enter your stored proc name here]", cn);
cmd.CommandType = CommandType.StoredProcedure;
this.Command = cmd;
return cmd.BeginExecuteReader();
}
public List<string> EndGetStuff(IAsyncResult r)
{
var dr = this.Command.EndExecuteReader(r);
var list = new List<string>();
while (dr.Read())
list.Add(dr[0].ToString());
return list;
}
I am providing a separate post to answer my question, since it's quite a long answer. I hope it helps others quickly implement async pattern in their WCF.
The points that I was missing when implementing async pattern in WCF, are as below. Without these, I was either getting a hung WCF problem saying 'Connecting...' or operation was aborted/canceled error message at WCF level. In my solution below, I have not discussed exception handling in async pattern on WCF side in order to keep it simple.
Do not invoke the EndGetProducts method of WCF by your code like calling it by using delagateInstance.Invoke or any other way. In async pattern, all you need to do is call the client-side callback, when your long async operation is complete, which will result in your client-side callback being called which in turn will call the WCF EndGetProduct method ( example: cb(asyncResult1) where cb is the callback delegate instance passed by the client-side code calling this WCF). I was trying to call the EndGetProducts WCF method by using Invoke, which is wrong. Even when client-side is passing nothing for client callback, this should still be done to invoke the End method in WCF.
Do not return the asyncresult you get from ADO.Net async begindatareader method, from BeginGetProducts method, since it needs to be the same AsyncResult that is in the context of client's call to WCF. This means you must include the client-side callback and the client-side state object in the AsyncResult that your BeginGetProducts will return, even when client-side is passing nothing for these. I was returning the AsyncResult of ADO.Net async method begindatareader from BeginGetProducts, which is wrong.
When calling the client-side callback delegate instance from WCF, make sure you pass the AsyncResult that contains client-side context that I have discussed in last bullet. Also, do this when your async operation is complete, which I do in the callback of beginexecutereader after I have created a List object.
One last point to keep in mind is that you must set sufficiently large timeouts at WCF and ADO.Net levels, since your async operation might take quite a long time else you will get timeouts in WCF. For this, set the ADO.Net command timeout to 0 ( infinite timeout) or to an appropriate value, and for WCF you can include configuration like below.
<binding name="legacyBinding" openTimeout="00:10:00" sendTimeout="00:10:00"
receiveTimeout="00:10:00" closeTimeout="00:10:00" maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647" >
Now the code, which might appear lengthy, but my intention is to make it easy for others to implement async pattern in their WCF. It was quite difficult for me.
WCF Contract
[OperationContract(AsyncPattern = true)]
[FaultContract(typeof(string))]
IAsyncResult BeginGetProducts(string vendorId, AsyncCallback cb, object s);
//The End method must return the actual datatype you intend to return from
//your async WCF operation. Also, do not decorate the End method with
//OperationContract or any other attribute
List<Product> EndGetProducts(IAsyncResult r);
WCF Implementation
public IAsyncResult BeginGetProducts( string vendorId, AsyncCallback cb, object s)
{
SqlCommand sqlCmd = null;
sqlCmd = new SqlCommand("dbo.ABC_sp_GetProducts", "Data Source=xyz;Initial Catalog=NorthwindNew;Integrated Security:true;asynchronous processing=true;"));
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.Parameters.AddWithValue("#vendorId", vendorId);
sqlCmd.CommandTimeout = 0;//async operations can be long operations so set a long timeout
//THIS ASYNRESULT MUST REFLECT THE CLIENT-SIDE STATE OBJECT, AND IT IS WHAT SHOULD FLOW THROUGH TO END METHOD of WCF.
//THE CLIENT CALLBACK (PARAMETER 'cb') SHOULD BE INVOKED USING THIS ASYNCRESULT, ELSE YOUR WCH WILL HANG OR YOUR WCF WILL GET ABORTED AUTOMATICALLY.
AsyncResult<FinalDataForDocumentsSummary> asyncResult1 = new AsyncResult<FinalDataForDocumentsSummary>(false, s);//this is the AsyncResult that should be used for any WCF-related method (not ADO.Net related)
AsyncCallback callback = new AsyncCallback(HandleCallback);//this is callback for ADO.Net async begindatareader method
sqlCmd.Connection.Open();
//AsynResult below is for passing information to ADO.Net asyn callback
AsyncResult<Product> cmdResult = new AsyncResult<Product>(false, new object[] {sqlCmd, cb,s});
sqlCmd.BeginExecuteReader(HandleCallback, cmdResult);
return asyncResult1;//ALWAYS RETURN THE ASYNCRESULT INSTANTIATED FROM CLIENT PARAMETER OF STATE OBJECT. FOR DATAREADER CREATE ANOTHER ASYNCRESULT THAT HAS COMMAND OBJECT INSIDE IT.
}
/// <summary>
/// This is the callback on WCF side for begin data reader method.
/// This is where you retrieve data, and put it into appropriate data objects to be returned to client.
/// Once data has been put into these objects, mark this ASYNC operation as complete and invoke the
/// client callback by using 'cb(asyncResult1)'. Use the same asyncresult that contains the client passed state object.
/// </summary>
/// <param name="result"></param>
public void HandleCallback(IAsyncResult result)
{
List<Product> summaries = new List<Product>();
Product product = null;
//THIS ASYNCRESULT IS ONLY FOR DATAREADER ASYNC METHOD AND NOT TO BE USED WITH WCF, ELSE BE READY FOR WCF FAILING
AsyncResult<Product> asyncResult = result.AsyncState as AsyncResult<Product>;
object[] objects = asyncResult.AsyncState as object[];
SqlCommand cmd = objects[0] as SqlCommand;
AsyncCallback cb = objects[1] as AsyncCallback;
object s = objects[2];
//CREATE THE SAME ASYNCRESULT THAT WE HAD IN BEGIN METHOD THAT USES THE CLIENT PASSED STATE OBJECT
AsyncResult<Product> asyncResult1 = new AsyncResult<Product>(false, s);
SqlDataReader dr = null;
if (cmd != null)
{
try
{
dr = cmd.EndExecuteReader(result);
while (dr.Read())
{
product = new Product(dr.GetInt32(0), dr.GetString(1));
summaries.Add(summary);
}
dr.Close();
cmd.Connection.Close();
//USE THE CORRECT ASYNCRESULT. WE NEED THE ASYNCRESULT THAT WE CREATED IN BEGIN METHOD OF WCF.
asyncResult1.Data = new FinalDataForDocumentsSummary(count, summaries.OrderByDescending(x => x.CountOfOverDue).ToList());
}
finally
{
if (dr != null)
{
dr.Close();
}
if (cmd.Connection != null)
{
cmd.Connection.Close();
cmd.Connection.Dispose();
}
//USE THE CORRECT ASYNCRESULT. WE NEED THE ASYNCRESULT THAT WE CREATED IN BEGIN METHOD OF WCF
asyncResult1.Complete();
//THIS IS REQUIRED ELSE WCF WILL HANG. EVEN WHEN NO CALLBACK IS PASSED BY CLIENT,
//YOU MUST EXECUTE THIS CODE. EXECUTE IT AFTER YOUR OPERATION HAS COMPLETED,
//SINCE THIS IS WHAT CAUSES THE END METHOD IN WCF TO EXECUTE.
//DON'T TRY TO CALL THE WCF END METHOD BY YOUR CODE (like using delegateInstance.Invoke) SINCE THIS WILL HANDLE IT.
cb(asyncResult1);
}
}
}
/// <summary>
/// This method gets automatically called by WCF if you include 'cb(asyncResult1)' in the reader's callback meethod, so don't try to call it by your code.
/// But always use 'cb(asyncResult1)' just after data has been successfully retrieved from database and operation is marked as complete.
/// </summary>
/// <param name="r"></param>
/// <returns></returns>
public List<Product> EndGetProducts(IAsyncResult r)
{
AsyncResult<Product> result = r as AsyncResult<Product>;
// Wait until the AsyncResult object indicates the
// operation is complete, in case the client called the End method just after the Begin method.
if (!result.CompletedSynchronously)
{
System.Threading.WaitHandle waitHandle = result.AsyncWaitHandle;
waitHandle.WaitOne();
}
// Return the database query results in the Data field
return result.Data;
}
Generic class for AsyncResult that is needed in async pattern
using System;
using System.Threading;
class AsyncResult<T> : IAsyncResult
{
private T data;
private object state;
private bool isCompleted = false;
private AutoResetEvent waitHandle;
private bool isSynchronous = false;
public T Data
{
set { data = value; }
get { return data; }
}
public AsyncResult(bool synchronous, object stateData)
{
isSynchronous = synchronous;
state = stateData;
}
public void Complete()
{
isCompleted = true;
((AutoResetEvent)AsyncWaitHandle).Set();
}
public object AsyncState
{
get { return state; }
}
public WaitHandle AsyncWaitHandle
{
get
{
if (waitHandle == null)
waitHandle = new AutoResetEvent(false);
return waitHandle;
}
}
public bool CompletedSynchronously
{
get
{
if (!isCompleted)
return false;
else
return isSynchronous;
}
}
public bool IsCompleted
{
get { return isCompleted; }
}
}
How to call this from client-side:
protected void Page_Load(object sender, EventArgs e)
{
using (ABCService.ServiceClient sc = new ABCService.ServiceClient())
{
// List<ABCService.Product> products = sc.GetDocSummary("Vend1", null, false);//this is synchronous call from client
sc.BeginGetProducts("Vend1",GetProductsCallback, sc);//this is asynchronous call from WCF
}
}
protected void GetProductsCallback(IAsyncResult asyncResult)
{
List<ABCService.Product> products = ((ABCService.ServiceClient)asyncResult.AsyncState).EndGetProducts(asyncResult);//this will call the WCF EndGetProducts method
}

WCF closing best practice

I read that the best practice for using WCF proxy would be:
YourClientProxy clientProxy = new YourClientProxy();
try
{
.. use your service
clientProxy.Close();
}
catch(FaultException)
{
clientProxy.Abort();
}
catch(CommunicationException)
{
clientProxy.Abort();
}
catch (TimeoutException)
{
clientProxy.Abort();
}
My problem is, after I allocate my proxy, I assign event handlers to it and also initialize other method using the proxy:
public void InitProxy()
{
sdksvc = new SdkServiceClient();
sdksvc.InitClusteringObjectCompleted += new EventHandler<InitClusteringObjectCompletedEventArgs>(sdksvc_InitClusteringObjectCompleted);
sdksvc.InitClusteringObjectAsync(Utils.DSN, Utils.USER,Utils.PASSWORD);
sdksvc.DoClusteringCompleted += new EventHandler<DoClusteringCompletedEventArgs>(sdksvc_DoClusteringCompleted);
sdksvc.CreateTablesCompleted += new EventHandler<CreateTablesCompletedEventArgs>(sdksvc_CreateTablesCompleted);
}
I now need to call the InitProxy() method each Time I use the proxy if I want to use it as best practice suggests.
Any ideas on how to avoid this?
There are several options. One option is to write a helper class as follows:
public class SvcClient : IDisposable {
public SvcClient(ICommunicationObject service) {
if( service == null ) {
throw ArgumentNullException("service");
}
_service = service;
// Add your event handlers here, e.g. using your example:
sdksvc = new SdkServiceClient();
sdksvc.InitClusteringObjectCompleted += new EventHandler<InitClusteringObjectCompletedEventArgs>(sdksvc_InitClusteringObjectCompleted);
sdksvc.InitClusteringObjectAsync(Utils.DSN, Utils.USER,Utils.PASSWORD);
sdksvc.DoClusteringCompleted += new EventHandler<DoClusteringCompletedEventArgs>(sdksvc_DoClusteringCompleted);
sdksvc.CreateTablesCompleted += new EventHandler<CreateTablesCompletedEventArgs>(sdksvc_CreateTablesCompleted);
}
public void Dispose() {
try {
if( _service.State == CommunicationState.Faulted ) {
_service.Abort();
}
}
finally {
_service.Close();
}
}
private readonly ICommunicationObject _service;
}
To use this class write the following:
var clientProxy = new YourClientProxy();
using(new SvcClient(clientProxy)) {
// use clientProxy as usual. No need to call Abort() and/or Close() here.
}
When the constructor for SvcClient is called it then sets up the SdkServiceClient instance as desired. Furthermore the SvcClient class cleans up the service client proxy as well aborting and/or closing the connection as needed regardless of how the control flow leaves the using-block.
I don't see how the ClientProxy and the InitProxy() are linked but if they are linked this strong I'd move the initialization of the ClientProxy to the InitProxy (or make a method that initializes both) so you can control both their lifespans from there.

Rollback transaction in Repository from another class

well my problem is:
I have a method like:
class Manager
{
void method1()
{
// save object in database to get ID
int newId = this.Repository.Save(obj);
try {
// call remote webservice to save another object with same ID as in local DB
webservice.Save(remoteObj, id);
}
catch(Exception e)
{
// do Rollback in Repository here
}
}
}
Bassically this is the code. Repository use NHibernate to save to DB. I need to save in DB to know the new ID and then send this ID to webservice. If something fail calling webservice I want to rollback and discard saved object.... and here is my problem. I can't open and control a transaction in Repository from my class Manager.
I already try with this also:
class Manager
{
void method1()
{
using (TransactionScope scope = new TransactionScope())
{
// save object in database to get ID
int newId = this.Repository.Save(obj);
// call remote webservice to save another object with same ID
// as in local DB
webservice.Save(remoteObj, id);
scope.Complete();
}
}
}
Here the problem is that the rollback is OK but not the Save(Create in NHibernate). I get error about that object "Transaction" is not found or the transaction is already closed just after the line : "scope.Complete();".
I think that something is wrong trying to control NHibernate transaction with TransactionScope .
I dont know if is a problem about approach, maybe another way should be used to handle this situation... ??
any help or idea where to find ??
Thanks a lot !!
Assuming you already have an opened session in a CurrentSession property/variable and that you could pass that working session to your repository, I would do the following:
using(var trx = CurrentSession.BeginTransaction())
{
try
{
int newId = this.Repository.Save(obj, CurrentSession);
webservice.Save(remoteObj, id);
trx.Commit();
}
catch
{
trx.Rollback();
}
}