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
}
Related
I'm trying to get the WCF service to run in InstanceContextMode.Single that way all requests can share the same state of the service. However, when I try to start the service with this behavior I can still see that the service's constructor gets called with each request. I couldn't figure out a quick way to update the ServiceBehaviorAttribute so that's why I'm replacing it (the default value for InstanceContextMode is not Single). Seems like there's one instance when we start it up and then another instance for all requests that come in later on. Any ideas what might be going wrong?
/// <summary>Constructor</summary>
CAutomation::CAutomation()
{
//TODO: pull from config
m_Host = gcnew ServiceHost(CAutomation::typeid, gcnew Uri("http://localhost:8001/GettingStarted"));
// add a service endpoint.
m_Host->AddServiceEndpoint(IAutomation::typeid, gcnew WSHttpBinding(), "Automation");
// add behavior
ServiceMetadataBehavior^ smb = gcnew ServiceMetadataBehavior();
smb->HttpGetEnabled = true;
m_Host->Description->Behaviors->Add(smb);
// enforce single instance behavior
m_Host->Description->Behaviors->RemoveAt(0);
ServiceBehaviorAttribute^ sba = gcnew ServiceBehaviorAttribute();
sba->InstanceContextMode = InstanceContextMode::Single;
m_Host->Description->Behaviors->Add(sba);
}
/// <summary>Starts the automation service.</summary>
void CAutomation::Start()
{
m_Host->Open();
}
Typically you set the ServiceBehaviorAttribute as a real attribute for the class that implements your service. I'm not C++/CLI expert, but I guess that since you're passing CAutomation::typeid to ServiceHost constructor, then CAutomation is your service class. Is that correct?
If so, then it should be enough to set ServiceBehaviorAttribute on the CAutomation class.
Igor Labutin pointed me in the right direction but the true issue here is that the creation of the service host object will create an instance of the class whose type is passed in to its constructor, at least when in [ServiceBehaviorAttribute(InstanceContextMode = InstanceContextMode::Single)]. Basically, the ServiceHost object should not have been the CAutomation class constructor. I moved that object outside of that constructor into another object which was responsible for when the service was supposed to start up and that corrected the issue. I'll paste a sample bit of code which helps to illustrate the better approach.
class Program
{
static void Main(string[] args)
{
Uri address = new Uri
("http://localhost:8080/QuickReturns/Exchange");
ServiceHost host = new ServiceHost(typeof(TradeService);
host.Open();
Console.WriteLine("Service started: Press Return to exit");
Console.ReadLine();
}
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,
ReturnUnknownExceptionsAsFaults=true)]
public class TradeService : ITradeService
{
private Hashtable tickers = new Hashtable();
public Quote GetQuote(string ticker)
{
lock (tickers)
{
Quote quote = tickers[ticker] as Quote;
if (quote == null)
{
// Quote doesn't exist
throw new Exception(
string.Format("No quotes found for ticker '{0}'",
ticker));
}
return quote;
}
}
public void PublishQuote(Quote quote)
{
lock (tickers)
{
Quote storedQuote = tickers[quote.Ticker] as Quote;
if (storedQuote == null)
{
tickers.Add(quote.Ticker, quote);
}
else
{
tickers[quote.Ticker] = quote;
}
}
}
}
I am using InstanceContextMode.Single in the sevice behaviour plus my service also implements the singleton pattern.
Such as:
private static readonly object padlock = new object();
public static MySingletonService Instance
{
get
{
lock (padlock)
{
if (_event == null)
{
_event = new MySingletonService();
}
return _event;
}
}
}
Now, the reason that I did this was I wanted to access the same instance of the service regardless of whether the call comes from a channel or someone simply calls it like a simple class(code snippets given for both scenarios).
var channelFactory = new ChannelFactory<MySingletonService>(myBinding, myEndpoint);
channelFactory.Open();
using (channelFactory)
{
var channelclient = channelFactory.CreateChannel();
channelclient.MethodCall(this);
}
Also,
MySingletonService.Instance.MethodCall(this);
But, I get two differenct objects (verified by checking object hashcode) in both cases and not one object.
Is there any other way to achieve this?
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));
}
I've created an integration test to verify that a repository handles Concurrency correcly. If I run the test without a TransactionScope, everything works as expect, but if I wrap the test in a TransactionScope, I get an error suggesting that there is a sudden need for distributed transactions (which lead me to believe that there is a second transaction being created). Here is the test:
[Test]
public void Commit_ItemToCommitContainsStaleData_ThrowsStaleObjectStateException()
{
using (new TransactionScope())
{
// arrange
RootUnitOfWorkFactory factory = CreateUnitOfWorkFactory();
const int Id = 1;
WorkItemRepository firstRepository = new WorkItemRepository(factory);
WorkItem itemToChange = WorkItem.Create(Id);
firstRepository.Commit(itemToChange);
WorkItemRepository secondRepository = new WorkItemRepository(factory);
WorkItem copyOfItemToChange = secondRepository.Get(Id);
// act
copyOfItemToChange.ChangeDescription("A");
secondRepository.Commit(copyOfItemToChange);
itemToChange.ChangeDescription("B");
// assert
Assert.Throws<StaleObjectStateException>(() => firstRepository.Commit(itemToChange));
}
}
This is the bottom of the error stack:
failed: NHibernate.Exceptions.GenericADOException : could not load an entity: [TfsTimeMachine.Domain.WorkItem#1][SQL: SELECT workitem0_.Id as Id1_0_, workitem0_.LastChanged as LastChan2_1_0_, workitem0_.Description as Descript3_1_0_ FROM [WorkItem] workitem0_ WHERE workitem0_.Id=?]
----> System.Data.SqlClient.SqlException : MSDTC on server 'ADM4200\SQLEXPRESS' is unavailable.
at NHibernate.Loader.Loader.LoadEntity(ISessionImplementor session, Object id, IType identifierType, Object optionalObject, String optionalEntityName, Object optionalIdentifier, IEntityPersister persister).
I'm running NUnit 2.1, so can someone tell me if Nhibernate creates implicit transactions if there is no session.BeginTransaction() before querying data, regardless of the session running within a TransactionScope?
I got this to work. The problem was (as stated in my comment) that two concurrent sessions were started within the same transactionscope and both started a new dbconnection which enlisted the same transaction, thus forcing DTC to kick in. The solution to this was to create a custom connection provider which ensured that the same connection was returned while inside a transactionscope. I then put this into play in my test and presto, I could test stale object state and rollback the data when the tests completes. Heres my implementation:
/// <summary>
/// A connection provider which returns the same db connetion while
/// there exists a TransactionScope.
/// </summary>
public sealed class AmbientTransactionAwareDriverConnectionProvider : IConnectionProvider
{
private readonly bool disposeDecoratedProviderWhenDisposingThis;
private IConnectionProvider decoratedProvider;
private IDbConnection maintainedConnectionThroughAmbientSession;
public AmbientTransactionAwareDriverConnectionProvider()
: this(new DriverConnectionProvider(), true)
{}
public AmbientTransactionAwareDriverConnectionProvider(IConnectionProvider decoratedProvider,
bool disposeDecoratedProviderWhenDisposingThis)
{
Guard.AssertNotNull(decoratedProvider, "decoratedProvider");
this.decoratedProvider = decoratedProvider;
this.disposeDecoratedProviderWhenDisposingThis = disposeDecoratedProviderWhenDisposingThis;
}
~AmbientTransactionAwareDriverConnectionProvider()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Configure(IDictionary<string, string> settings)
{
this.decoratedProvider.Configure(settings);
}
public void CloseConnection(IDbConnection conn)
{
if (Transaction.Current == null)
this.decoratedProvider.CloseConnection(conn);
}
public IDbConnection GetConnection()
{
if (Transaction.Current == null)
{
if (this.maintainedConnectionThroughAmbientSession != null)
this.maintainedConnectionThroughAmbientSession.Dispose();
return this.decoratedProvider.GetConnection();
}
if (this.maintainedConnectionThroughAmbientSession == null)
this.maintainedConnectionThroughAmbientSession = this.decoratedProvider.GetConnection();
return this.maintainedConnectionThroughAmbientSession;
}
private void Dispose(bool disposing)
{
if (this.maintainedConnectionThroughAmbientSession != null)
CloseConnection(this.maintainedConnectionThroughAmbientSession);
if (this.disposeDecoratedProviderWhenDisposingThis && this.decoratedProvider != null)
this.decoratedProvider.Dispose();
if (disposing)
{
this.decoratedProvider = null;
this.maintainedConnectionThroughAmbientSession = null;
}
}
public IDriver Driver
{
get { return this.decoratedProvider.Driver; }
}
}
I'm not sure if Hibernate is using transactions internally, but I also don't think that is your problem here.
It appears that the problem is that you are using two different data sources in the same transaction. In order to coordinate the transaction between both data sources for a two-phase commit, you would need to have DTC enabled. The fact that both data sources are actually the same database is immaterial.
If you call a web service from Silverlight like this:
MyServiceClient serviceClient = new MyServiceClient();
void MyMethod()
{
serviceClient.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(serviceClient_GetDataCompleted);
serviceClient.GetDataAsync();
// HOW DO I WAIT/JOIN HERE ON THE ASYNC CALL, RATHER THAN BEING FORCE TO LEAVE THIS METHOD?
}
I would rather wait/join with the asych service thread inside "MyMethod" rather than leaving "MyMethod" after calling "GetDataAsync", what is the best way to do this?
Thanks,
Jeff
No you cannot do this way. You will end up in a deadlock. GetDataCompleted is called by the mainthreed. The same threed thait is waiting in WaitOne.
I have to ask; why? The point is to provide your user with a fluid experience and waiting on a web service call will not necessarily do that. I suppose you want the full block of content to load before the Silverlight control loads. In that case, I would turn to caching the content rather than forcing the client to wait indefinitely.
To do this you would use a ManualResetEvent in your class (class level variable) and then wait on it.
void MyMethod()
{
wait = new ManualResetEvent(false);
// call your service
wait.WaitOne();
// finish working
}
and in your event handler code
void serviceClient_GetDataCompleted(...)
{
// Set values you need from service
wait.Set();
}
You could also use a lambda and closure to get similar behavior:
serviceClient.GetDataCompleted += (s,e) =>
{
// Your code here
};
serviceClient.GetDataAsync();
If you had a base class provide the mechanics of building a WCF channel, it could then be used to build the BeginX / EndX methods for a async call.
public class ServiceFooCoordinator : CoordinatorBase<IServiceFoo>
{
public IAsyncResult BeginMethodFoo ()
{
IAsyncResult ar = null;
IServiceFoo channel = null;
channel = _factory.GetChannel();
Begin( channel, () => ar = channel.BeginMethodFoo( null, channel ) );
return ar;
}
public Bar[] EndMethodFoo ( IAsyncResult ar )
{
IServiceFoo channel = null;
channel = _factory.GetChannel();
return channel.EndMethodFoo( ar );
}
}
Which can then be used in a method:
ServiceFooCoordinator _coordinator;
var asyncResult = _coordinator.BeginMethodFoo();
try
{
var result = _coordinator.EndMethodFoo( asyncResult );
}
catch ( Exception )
{ }
Which gets you your asynchronous call in a sychronous manner.