Issue in asynchronous API calls from SQLCLR
In a nutshell, I need to notify a Web API service from SQL Server asynchronously as and when there are changes in a particular table.
To achieve the above, I have created a SQLCLR stored procedure which contains the asynchronous API call to notify the service. The SQLCLR stored procedure is called via a trigger as and when there is an insert into a table called Table1
. The main challenge here is API has to read data from the same table (Table1
).
If I use HttpWebRequest.GetResponse()
which is the synchronous version, the entire operation is getting locked out due to the implicit lock of the insert trigger. To avoid this, I have used HttpWebRequest.GetResponseAsync()
method which calls the API and doesn't wait for the response. So it fires the API request and the program control moves on so the trigger transaction doesn't hold any lock(s) on table1
and the API was able to read data from table1
.
Now I have to implement an error notification mechanism as and when there are failures (like unable to connect to remote server) and I need to send an email to the admin team. I have wrote the mail composition logic inside the catch()
block. If I proceed with the above HttpWebRequest.GetResponseAsync().Result
method, the entire operation becomes synchronous and it locks the entire operation.
If I use the BeginGetResponse()
and EndGetResponse()
method implementation suggested in Microsoft documents and run the SQLCLR stored procedure, SQL Server hangs without any information, why? What am I doing wrong here? Why does the RespCallback()
method not get executed?
Sharing the SQLCLR code snippets below.
public class RequestState
{
// This class stores the State of the request.
// const int BUFFER_SIZE = 1024;
// public StringBuilder requestData;
// public byte BufferRead;
public HttpWebRequest request;
public HttpWebResponse response;
// public Stream streamResponse;
public RequestState()
{
// BufferRead = new byte[BUFFER_SIZE];
// requestData = new StringBuilder("");
request = null;
// streamResponse = null;
}
}
public partial class StoredProcedures
{
private static SqlString _mailServer = null;
private static SqlString _port = null;
private static SqlString _fromAddress = null;
private static SqlString _toAddress = null;
private static SqlString _mailAcctUserName = null;
private static SqlString _decryptedPassword = null;
private static SqlString _subject = null;
private static string _mailContent = null;
private static int _portNo = 0;
public static ManualResetEvent allDone = new ManualResetEvent(false);
const int DefaultTimeout = 20000; // 50 seconds timeout
#region TimeOutCallBack
/// <summary>
/// Abort the request if the timer fires.
/// </summary>
/// <param name="state">request state</param>
/// <param name="timedOut">timeout status</param>
private static void TimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
HttpWebRequest request = state as HttpWebRequest;
if (request != null)
{
request.Abort();
SendNotifyErrorEmail(null, "The request got timedOut!,please check the API");
}
}
}
#endregion
#region APINotification
[SqlProcedure]
public static void Notify(SqlString weburl, SqlString username, SqlString password, SqlString connectionLimit, SqlString mailServer, SqlString port, SqlString fromAddress
, SqlString toAddress, SqlString mailAcctUserName, SqlString mailAcctPassword, SqlString subject)
{
_mailServer = mailServer;
_port = port;
_fromAddress = fromAddress;
_toAddress = toAddress;
_mailAcctUserName = mailAcctUserName;
_decryptedPassword = mailAcctPassword;
_subject = subject;
if (!(weburl.IsNull && username.IsNull && password.IsNull && connectionLimit.IsNull))
{
var url = Convert.ToString(weburl);
var uname = Convert.ToString(username);
var pass = Convert.ToString(password);
var connLimit = Convert.ToString(connectionLimit);
int conLimit = Convert.ToInt32(connLimit);
try
{
if (!(string.IsNullOrEmpty(url) && string.IsNullOrEmpty(uname) && string.IsNullOrEmpty(pass) && conLimit > 0))
{
SqlContext.Pipe.Send("Entered inside the notify method");
HttpWebRequest httpWebRequest = WebRequest.Create(url) as HttpWebRequest;
string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(uname + ":" + pass));
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
httpWebRequest.Method = "POST";
httpWebRequest.ContentLength = 0;
httpWebRequest.ServicePoint.ConnectionLimit = conLimit;
// Create an instance of the RequestState and assign the previous myHttpWebRequest
// object to its request field.
RequestState requestState = new RequestState();
requestState.request = httpWebRequest;
SqlContext.Pipe.Send("before sending the notification");
//Start the asynchronous request.
IAsyncResult result =
(IAsyncResult)httpWebRequest.BeginGetResponse(new AsyncCallback(RespCallback), requestState);
SqlContext.Pipe.Send("after BeginGetResponse");
// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), requestState, DefaultTimeout, true);
//SqlContext.Pipe.Send("after RegisterWaitForSingleObject");
// The response came in the allowed time. The work processing will happen in the
// callback function.
allDone.WaitOne();
//SqlContext.Pipe.Send("after allDone.WaitOne();");
// Release the HttpWebResponse resource.
requestState.response.Close();
SqlContext.Pipe.Send("after requestState.response.Close()");
}
}
catch (Exception exception)
{
SqlContext.Pipe.Send(" Main Exception");
SqlContext.Pipe.Send(exception.Message.ToString());
//TODO: log the details in a error table
SendNotifyErrorEmail(exception, null);
}
}
}
#endregion
#region ResposnseCallBack
/// <summary>
/// asynchronous Httpresponse callback
/// </summary>
/// <param name="asynchronousResult"></param>
private static void RespCallback(IAsyncResult asynchronousResult)
{
try
{
SqlContext.Pipe.Send("Entering the respcallback");
// State of request is asynchronous.
RequestState httpRequestState = (RequestState)asynchronousResult.AsyncState;
HttpWebRequest currentHttpWebRequest = httpRequestState.request;
httpRequestState.response = (HttpWebResponse)currentHttpWebRequest.EndGetResponse(asynchronousResult);
SqlContext.Pipe.Send("exiting the respcallBack");
}
catch (Exception ex)
{
SqlContext.Pipe.Send("exception in the respcallBack");
SendNotifyErrorEmail(ex, null);
}
allDone.Set();
}
#endregion
}
One alternative approach for above is using SQL Server Service Broker which has the queuing mechanism which will help us to implement asynchronous triggers. But do we have any solution for the above situation? Am I doing anything wrong in an approach perspective? Please guide me.
c# .net sql-server async-await sqlclr
add a comment |
In a nutshell, I need to notify a Web API service from SQL Server asynchronously as and when there are changes in a particular table.
To achieve the above, I have created a SQLCLR stored procedure which contains the asynchronous API call to notify the service. The SQLCLR stored procedure is called via a trigger as and when there is an insert into a table called Table1
. The main challenge here is API has to read data from the same table (Table1
).
If I use HttpWebRequest.GetResponse()
which is the synchronous version, the entire operation is getting locked out due to the implicit lock of the insert trigger. To avoid this, I have used HttpWebRequest.GetResponseAsync()
method which calls the API and doesn't wait for the response. So it fires the API request and the program control moves on so the trigger transaction doesn't hold any lock(s) on table1
and the API was able to read data from table1
.
Now I have to implement an error notification mechanism as and when there are failures (like unable to connect to remote server) and I need to send an email to the admin team. I have wrote the mail composition logic inside the catch()
block. If I proceed with the above HttpWebRequest.GetResponseAsync().Result
method, the entire operation becomes synchronous and it locks the entire operation.
If I use the BeginGetResponse()
and EndGetResponse()
method implementation suggested in Microsoft documents and run the SQLCLR stored procedure, SQL Server hangs without any information, why? What am I doing wrong here? Why does the RespCallback()
method not get executed?
Sharing the SQLCLR code snippets below.
public class RequestState
{
// This class stores the State of the request.
// const int BUFFER_SIZE = 1024;
// public StringBuilder requestData;
// public byte BufferRead;
public HttpWebRequest request;
public HttpWebResponse response;
// public Stream streamResponse;
public RequestState()
{
// BufferRead = new byte[BUFFER_SIZE];
// requestData = new StringBuilder("");
request = null;
// streamResponse = null;
}
}
public partial class StoredProcedures
{
private static SqlString _mailServer = null;
private static SqlString _port = null;
private static SqlString _fromAddress = null;
private static SqlString _toAddress = null;
private static SqlString _mailAcctUserName = null;
private static SqlString _decryptedPassword = null;
private static SqlString _subject = null;
private static string _mailContent = null;
private static int _portNo = 0;
public static ManualResetEvent allDone = new ManualResetEvent(false);
const int DefaultTimeout = 20000; // 50 seconds timeout
#region TimeOutCallBack
/// <summary>
/// Abort the request if the timer fires.
/// </summary>
/// <param name="state">request state</param>
/// <param name="timedOut">timeout status</param>
private static void TimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
HttpWebRequest request = state as HttpWebRequest;
if (request != null)
{
request.Abort();
SendNotifyErrorEmail(null, "The request got timedOut!,please check the API");
}
}
}
#endregion
#region APINotification
[SqlProcedure]
public static void Notify(SqlString weburl, SqlString username, SqlString password, SqlString connectionLimit, SqlString mailServer, SqlString port, SqlString fromAddress
, SqlString toAddress, SqlString mailAcctUserName, SqlString mailAcctPassword, SqlString subject)
{
_mailServer = mailServer;
_port = port;
_fromAddress = fromAddress;
_toAddress = toAddress;
_mailAcctUserName = mailAcctUserName;
_decryptedPassword = mailAcctPassword;
_subject = subject;
if (!(weburl.IsNull && username.IsNull && password.IsNull && connectionLimit.IsNull))
{
var url = Convert.ToString(weburl);
var uname = Convert.ToString(username);
var pass = Convert.ToString(password);
var connLimit = Convert.ToString(connectionLimit);
int conLimit = Convert.ToInt32(connLimit);
try
{
if (!(string.IsNullOrEmpty(url) && string.IsNullOrEmpty(uname) && string.IsNullOrEmpty(pass) && conLimit > 0))
{
SqlContext.Pipe.Send("Entered inside the notify method");
HttpWebRequest httpWebRequest = WebRequest.Create(url) as HttpWebRequest;
string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(uname + ":" + pass));
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
httpWebRequest.Method = "POST";
httpWebRequest.ContentLength = 0;
httpWebRequest.ServicePoint.ConnectionLimit = conLimit;
// Create an instance of the RequestState and assign the previous myHttpWebRequest
// object to its request field.
RequestState requestState = new RequestState();
requestState.request = httpWebRequest;
SqlContext.Pipe.Send("before sending the notification");
//Start the asynchronous request.
IAsyncResult result =
(IAsyncResult)httpWebRequest.BeginGetResponse(new AsyncCallback(RespCallback), requestState);
SqlContext.Pipe.Send("after BeginGetResponse");
// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), requestState, DefaultTimeout, true);
//SqlContext.Pipe.Send("after RegisterWaitForSingleObject");
// The response came in the allowed time. The work processing will happen in the
// callback function.
allDone.WaitOne();
//SqlContext.Pipe.Send("after allDone.WaitOne();");
// Release the HttpWebResponse resource.
requestState.response.Close();
SqlContext.Pipe.Send("after requestState.response.Close()");
}
}
catch (Exception exception)
{
SqlContext.Pipe.Send(" Main Exception");
SqlContext.Pipe.Send(exception.Message.ToString());
//TODO: log the details in a error table
SendNotifyErrorEmail(exception, null);
}
}
}
#endregion
#region ResposnseCallBack
/// <summary>
/// asynchronous Httpresponse callback
/// </summary>
/// <param name="asynchronousResult"></param>
private static void RespCallback(IAsyncResult asynchronousResult)
{
try
{
SqlContext.Pipe.Send("Entering the respcallback");
// State of request is asynchronous.
RequestState httpRequestState = (RequestState)asynchronousResult.AsyncState;
HttpWebRequest currentHttpWebRequest = httpRequestState.request;
httpRequestState.response = (HttpWebResponse)currentHttpWebRequest.EndGetResponse(asynchronousResult);
SqlContext.Pipe.Send("exiting the respcallBack");
}
catch (Exception ex)
{
SqlContext.Pipe.Send("exception in the respcallBack");
SendNotifyErrorEmail(ex, null);
}
allDone.Set();
}
#endregion
}
One alternative approach for above is using SQL Server Service Broker which has the queuing mechanism which will help us to implement asynchronous triggers. But do we have any solution for the above situation? Am I doing anything wrong in an approach perspective? Please guide me.
c# .net sql-server async-await sqlclr
As far as I understand your service also tries to read that newly added data from the same table to return the result. You can avoid this situation by copying that newly added data to another table like "table1_new" or change your workflow to support eventual consistency instead of ACID. If you use eventual consistency you can send this message to a message bus and do not wait for the result. You can also use a database change tracker (like linkedin Databus) to track changes in your tables without touching to your actual database.
– NthDeveloper
Jan 1 at 14:41
add a comment |
In a nutshell, I need to notify a Web API service from SQL Server asynchronously as and when there are changes in a particular table.
To achieve the above, I have created a SQLCLR stored procedure which contains the asynchronous API call to notify the service. The SQLCLR stored procedure is called via a trigger as and when there is an insert into a table called Table1
. The main challenge here is API has to read data from the same table (Table1
).
If I use HttpWebRequest.GetResponse()
which is the synchronous version, the entire operation is getting locked out due to the implicit lock of the insert trigger. To avoid this, I have used HttpWebRequest.GetResponseAsync()
method which calls the API and doesn't wait for the response. So it fires the API request and the program control moves on so the trigger transaction doesn't hold any lock(s) on table1
and the API was able to read data from table1
.
Now I have to implement an error notification mechanism as and when there are failures (like unable to connect to remote server) and I need to send an email to the admin team. I have wrote the mail composition logic inside the catch()
block. If I proceed with the above HttpWebRequest.GetResponseAsync().Result
method, the entire operation becomes synchronous and it locks the entire operation.
If I use the BeginGetResponse()
and EndGetResponse()
method implementation suggested in Microsoft documents and run the SQLCLR stored procedure, SQL Server hangs without any information, why? What am I doing wrong here? Why does the RespCallback()
method not get executed?
Sharing the SQLCLR code snippets below.
public class RequestState
{
// This class stores the State of the request.
// const int BUFFER_SIZE = 1024;
// public StringBuilder requestData;
// public byte BufferRead;
public HttpWebRequest request;
public HttpWebResponse response;
// public Stream streamResponse;
public RequestState()
{
// BufferRead = new byte[BUFFER_SIZE];
// requestData = new StringBuilder("");
request = null;
// streamResponse = null;
}
}
public partial class StoredProcedures
{
private static SqlString _mailServer = null;
private static SqlString _port = null;
private static SqlString _fromAddress = null;
private static SqlString _toAddress = null;
private static SqlString _mailAcctUserName = null;
private static SqlString _decryptedPassword = null;
private static SqlString _subject = null;
private static string _mailContent = null;
private static int _portNo = 0;
public static ManualResetEvent allDone = new ManualResetEvent(false);
const int DefaultTimeout = 20000; // 50 seconds timeout
#region TimeOutCallBack
/// <summary>
/// Abort the request if the timer fires.
/// </summary>
/// <param name="state">request state</param>
/// <param name="timedOut">timeout status</param>
private static void TimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
HttpWebRequest request = state as HttpWebRequest;
if (request != null)
{
request.Abort();
SendNotifyErrorEmail(null, "The request got timedOut!,please check the API");
}
}
}
#endregion
#region APINotification
[SqlProcedure]
public static void Notify(SqlString weburl, SqlString username, SqlString password, SqlString connectionLimit, SqlString mailServer, SqlString port, SqlString fromAddress
, SqlString toAddress, SqlString mailAcctUserName, SqlString mailAcctPassword, SqlString subject)
{
_mailServer = mailServer;
_port = port;
_fromAddress = fromAddress;
_toAddress = toAddress;
_mailAcctUserName = mailAcctUserName;
_decryptedPassword = mailAcctPassword;
_subject = subject;
if (!(weburl.IsNull && username.IsNull && password.IsNull && connectionLimit.IsNull))
{
var url = Convert.ToString(weburl);
var uname = Convert.ToString(username);
var pass = Convert.ToString(password);
var connLimit = Convert.ToString(connectionLimit);
int conLimit = Convert.ToInt32(connLimit);
try
{
if (!(string.IsNullOrEmpty(url) && string.IsNullOrEmpty(uname) && string.IsNullOrEmpty(pass) && conLimit > 0))
{
SqlContext.Pipe.Send("Entered inside the notify method");
HttpWebRequest httpWebRequest = WebRequest.Create(url) as HttpWebRequest;
string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(uname + ":" + pass));
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
httpWebRequest.Method = "POST";
httpWebRequest.ContentLength = 0;
httpWebRequest.ServicePoint.ConnectionLimit = conLimit;
// Create an instance of the RequestState and assign the previous myHttpWebRequest
// object to its request field.
RequestState requestState = new RequestState();
requestState.request = httpWebRequest;
SqlContext.Pipe.Send("before sending the notification");
//Start the asynchronous request.
IAsyncResult result =
(IAsyncResult)httpWebRequest.BeginGetResponse(new AsyncCallback(RespCallback), requestState);
SqlContext.Pipe.Send("after BeginGetResponse");
// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), requestState, DefaultTimeout, true);
//SqlContext.Pipe.Send("after RegisterWaitForSingleObject");
// The response came in the allowed time. The work processing will happen in the
// callback function.
allDone.WaitOne();
//SqlContext.Pipe.Send("after allDone.WaitOne();");
// Release the HttpWebResponse resource.
requestState.response.Close();
SqlContext.Pipe.Send("after requestState.response.Close()");
}
}
catch (Exception exception)
{
SqlContext.Pipe.Send(" Main Exception");
SqlContext.Pipe.Send(exception.Message.ToString());
//TODO: log the details in a error table
SendNotifyErrorEmail(exception, null);
}
}
}
#endregion
#region ResposnseCallBack
/// <summary>
/// asynchronous Httpresponse callback
/// </summary>
/// <param name="asynchronousResult"></param>
private static void RespCallback(IAsyncResult asynchronousResult)
{
try
{
SqlContext.Pipe.Send("Entering the respcallback");
// State of request is asynchronous.
RequestState httpRequestState = (RequestState)asynchronousResult.AsyncState;
HttpWebRequest currentHttpWebRequest = httpRequestState.request;
httpRequestState.response = (HttpWebResponse)currentHttpWebRequest.EndGetResponse(asynchronousResult);
SqlContext.Pipe.Send("exiting the respcallBack");
}
catch (Exception ex)
{
SqlContext.Pipe.Send("exception in the respcallBack");
SendNotifyErrorEmail(ex, null);
}
allDone.Set();
}
#endregion
}
One alternative approach for above is using SQL Server Service Broker which has the queuing mechanism which will help us to implement asynchronous triggers. But do we have any solution for the above situation? Am I doing anything wrong in an approach perspective? Please guide me.
c# .net sql-server async-await sqlclr
In a nutshell, I need to notify a Web API service from SQL Server asynchronously as and when there are changes in a particular table.
To achieve the above, I have created a SQLCLR stored procedure which contains the asynchronous API call to notify the service. The SQLCLR stored procedure is called via a trigger as and when there is an insert into a table called Table1
. The main challenge here is API has to read data from the same table (Table1
).
If I use HttpWebRequest.GetResponse()
which is the synchronous version, the entire operation is getting locked out due to the implicit lock of the insert trigger. To avoid this, I have used HttpWebRequest.GetResponseAsync()
method which calls the API and doesn't wait for the response. So it fires the API request and the program control moves on so the trigger transaction doesn't hold any lock(s) on table1
and the API was able to read data from table1
.
Now I have to implement an error notification mechanism as and when there are failures (like unable to connect to remote server) and I need to send an email to the admin team. I have wrote the mail composition logic inside the catch()
block. If I proceed with the above HttpWebRequest.GetResponseAsync().Result
method, the entire operation becomes synchronous and it locks the entire operation.
If I use the BeginGetResponse()
and EndGetResponse()
method implementation suggested in Microsoft documents and run the SQLCLR stored procedure, SQL Server hangs without any information, why? What am I doing wrong here? Why does the RespCallback()
method not get executed?
Sharing the SQLCLR code snippets below.
public class RequestState
{
// This class stores the State of the request.
// const int BUFFER_SIZE = 1024;
// public StringBuilder requestData;
// public byte BufferRead;
public HttpWebRequest request;
public HttpWebResponse response;
// public Stream streamResponse;
public RequestState()
{
// BufferRead = new byte[BUFFER_SIZE];
// requestData = new StringBuilder("");
request = null;
// streamResponse = null;
}
}
public partial class StoredProcedures
{
private static SqlString _mailServer = null;
private static SqlString _port = null;
private static SqlString _fromAddress = null;
private static SqlString _toAddress = null;
private static SqlString _mailAcctUserName = null;
private static SqlString _decryptedPassword = null;
private static SqlString _subject = null;
private static string _mailContent = null;
private static int _portNo = 0;
public static ManualResetEvent allDone = new ManualResetEvent(false);
const int DefaultTimeout = 20000; // 50 seconds timeout
#region TimeOutCallBack
/// <summary>
/// Abort the request if the timer fires.
/// </summary>
/// <param name="state">request state</param>
/// <param name="timedOut">timeout status</param>
private static void TimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
HttpWebRequest request = state as HttpWebRequest;
if (request != null)
{
request.Abort();
SendNotifyErrorEmail(null, "The request got timedOut!,please check the API");
}
}
}
#endregion
#region APINotification
[SqlProcedure]
public static void Notify(SqlString weburl, SqlString username, SqlString password, SqlString connectionLimit, SqlString mailServer, SqlString port, SqlString fromAddress
, SqlString toAddress, SqlString mailAcctUserName, SqlString mailAcctPassword, SqlString subject)
{
_mailServer = mailServer;
_port = port;
_fromAddress = fromAddress;
_toAddress = toAddress;
_mailAcctUserName = mailAcctUserName;
_decryptedPassword = mailAcctPassword;
_subject = subject;
if (!(weburl.IsNull && username.IsNull && password.IsNull && connectionLimit.IsNull))
{
var url = Convert.ToString(weburl);
var uname = Convert.ToString(username);
var pass = Convert.ToString(password);
var connLimit = Convert.ToString(connectionLimit);
int conLimit = Convert.ToInt32(connLimit);
try
{
if (!(string.IsNullOrEmpty(url) && string.IsNullOrEmpty(uname) && string.IsNullOrEmpty(pass) && conLimit > 0))
{
SqlContext.Pipe.Send("Entered inside the notify method");
HttpWebRequest httpWebRequest = WebRequest.Create(url) as HttpWebRequest;
string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(uname + ":" + pass));
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
httpWebRequest.Method = "POST";
httpWebRequest.ContentLength = 0;
httpWebRequest.ServicePoint.ConnectionLimit = conLimit;
// Create an instance of the RequestState and assign the previous myHttpWebRequest
// object to its request field.
RequestState requestState = new RequestState();
requestState.request = httpWebRequest;
SqlContext.Pipe.Send("before sending the notification");
//Start the asynchronous request.
IAsyncResult result =
(IAsyncResult)httpWebRequest.BeginGetResponse(new AsyncCallback(RespCallback), requestState);
SqlContext.Pipe.Send("after BeginGetResponse");
// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), requestState, DefaultTimeout, true);
//SqlContext.Pipe.Send("after RegisterWaitForSingleObject");
// The response came in the allowed time. The work processing will happen in the
// callback function.
allDone.WaitOne();
//SqlContext.Pipe.Send("after allDone.WaitOne();");
// Release the HttpWebResponse resource.
requestState.response.Close();
SqlContext.Pipe.Send("after requestState.response.Close()");
}
}
catch (Exception exception)
{
SqlContext.Pipe.Send(" Main Exception");
SqlContext.Pipe.Send(exception.Message.ToString());
//TODO: log the details in a error table
SendNotifyErrorEmail(exception, null);
}
}
}
#endregion
#region ResposnseCallBack
/// <summary>
/// asynchronous Httpresponse callback
/// </summary>
/// <param name="asynchronousResult"></param>
private static void RespCallback(IAsyncResult asynchronousResult)
{
try
{
SqlContext.Pipe.Send("Entering the respcallback");
// State of request is asynchronous.
RequestState httpRequestState = (RequestState)asynchronousResult.AsyncState;
HttpWebRequest currentHttpWebRequest = httpRequestState.request;
httpRequestState.response = (HttpWebResponse)currentHttpWebRequest.EndGetResponse(asynchronousResult);
SqlContext.Pipe.Send("exiting the respcallBack");
}
catch (Exception ex)
{
SqlContext.Pipe.Send("exception in the respcallBack");
SendNotifyErrorEmail(ex, null);
}
allDone.Set();
}
#endregion
}
One alternative approach for above is using SQL Server Service Broker which has the queuing mechanism which will help us to implement asynchronous triggers. But do we have any solution for the above situation? Am I doing anything wrong in an approach perspective? Please guide me.
c# .net sql-server async-await sqlclr
c# .net sql-server async-await sqlclr
edited Jan 1 at 10:48
marc_s
578k12911161261
578k12911161261
asked Jan 1 at 9:11
Jose FrancisJose Francis
746918
746918
As far as I understand your service also tries to read that newly added data from the same table to return the result. You can avoid this situation by copying that newly added data to another table like "table1_new" or change your workflow to support eventual consistency instead of ACID. If you use eventual consistency you can send this message to a message bus and do not wait for the result. You can also use a database change tracker (like linkedin Databus) to track changes in your tables without touching to your actual database.
– NthDeveloper
Jan 1 at 14:41
add a comment |
As far as I understand your service also tries to read that newly added data from the same table to return the result. You can avoid this situation by copying that newly added data to another table like "table1_new" or change your workflow to support eventual consistency instead of ACID. If you use eventual consistency you can send this message to a message bus and do not wait for the result. You can also use a database change tracker (like linkedin Databus) to track changes in your tables without touching to your actual database.
– NthDeveloper
Jan 1 at 14:41
As far as I understand your service also tries to read that newly added data from the same table to return the result. You can avoid this situation by copying that newly added data to another table like "table1_new" or change your workflow to support eventual consistency instead of ACID. If you use eventual consistency you can send this message to a message bus and do not wait for the result. You can also use a database change tracker (like linkedin Databus) to track changes in your tables without touching to your actual database.
– NthDeveloper
Jan 1 at 14:41
As far as I understand your service also tries to read that newly added data from the same table to return the result. You can avoid this situation by copying that newly added data to another table like "table1_new" or change your workflow to support eventual consistency instead of ACID. If you use eventual consistency you can send this message to a message bus and do not wait for the result. You can also use a database change tracker (like linkedin Databus) to track changes in your tables without touching to your actual database.
– NthDeveloper
Jan 1 at 14:41
add a comment |
2 Answers
2
active
oldest
votes
There are a couple of things that stand out as possible issues:
- Wouldn't the call to
allDone.WaitOne();
block until a response is received anyway, negating the need / use of all this async stuff? - Even if this does work, are you testing this in a single session? You have several static member (class-level) variables, such as
public static ManualResetEvent allDone
, that are shared across all sessions. SQLCLR uses a shared App Domain (App Domains are per Database / per Assembly owner). Hence multiple sessions will over-write each other's values of these shared static variables. That is very dangerous (hence why non-readonly static variables are only allowed inUNSAFE
Assemblies). This model only works if you can guarantee a single caller at any given moment.
Beyond any SQLCLR technicalities, I am not sure that this is a good model even if you do manage to get past this particular issue.
A better, safer model would be to:
- create a queue table to log these changes. you typically only need the key columns and a timestamp (
DATETIME
orDATETIME2
, not theTIMESTAMP
datatype). - have the trigger log the current time and rows modified into the queue table
create a stored procedure that takes items from the queue, starting with the oldest records, processes them (which can definitely be calling your SQLCLR stored procedure to do the web server call, but no need for it to be async, so remove that stuff and set the Assembly back to
EXTERNAL_ACCESS
since you don't need/wantUNSAFE
).
Do this in a transaction so that the records are not fully removed from the queue table if the "processing" fails. Sometimes using the
OUTPUT
clause withDELETE
to reserve rows that you are working on into a local temp table helps.
Process multiple records at a time, even if calling the SQLCLR stored procedure needs to be done on a per-row basis.
- create a SQL Server agent job to execute the stored procedure every minute (or less depending on need)
Minor issues:
- the pattern of copying input parameters into static variables (e.g.
_mailServer = mailServer;
) is pointless at best, and error-prone regardless due to not being thread-safe. remember, static variables are shared across all sessions, so any concurrent sessions will be overwriting the previous values, hence ensure race conditions. Please remove all of the variables with names starting with an underscore. - the pattern of using
Convert.To...
is also unnecessary and a slight hit to performance. AllSql*
types have aValue
property that returns the expected .NET type. Hence, you only need:string url = weburl.Value;
- there is no need to use incorrect datatypes that require conversion later on. Meaning, rather than using
SqlString
forconnectionLimit
, instead useSqlInt32
and then you can simply doint connLimit = connectionLimit.Value;
- You probably don't need to do the security manually (i.e.
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
). I am pretty sure you can just create a new NetworkCredential usinguname
andpass
and assign that to the request.
In SQL CLR AppDomains are not long-lived. Your AppDomain may be unloaded or recycled at any time, and in this model SQL Server won't wait for your HTTP calls to complete before shutting it down. So this won't be reliable. Also to get rid of the static variables, capture the local variables to pass to the callback.
– David Browne - Microsoft
Jan 2 at 16:48
@DavidBrowne-Microsoft Good points, thanks. I was about to add the first one to my answer but then remembered that App Domains are not unloaded immediately: they are marked as "doomed" and do allow for current running threads to complete, while new requests will spin up a new App Domain. In this case, both App Domains can exist concurrently, though they cannot see each other and there is no way to access the "doomed" App Domain. Only caveat here is that I haven't tested with async requests, but I don't believe that the behavior would be any different.
– Solomon Rutzky
Jan 2 at 16:59
I don't think SQL knows about the pending async request, as no thread is waiting on the response. It may, somehow, but it's not documented or simple to test.
– David Browne - Microsoft
Jan 2 at 17:05
add a comment |
Hi there (and Happy New Year) :)!
A couple of things:
If you can, stay away from triggers! They can cause a lot of unexpected side effects, especially if you use them for business logic. What I mean with that, is that they are good for auditing purposes, but apart from that I try and avoid them like the plague.
So, if you do not use triggers, how do you know when something is inserted? Well, I hope that your inserts happen through stored procedures, and are not direct inserts in the table(s). If you use stored procedures you can have a procedure which is doing your logic, and that procedure is called from the procedure which does the insert.
Back to your question. I don't really have an answer to the specifics, but I would stay away from using SQLCLR in this case (sidenote: I am a BIG proponent of SQLCLR, and I have implemented a lot of SQLCLR processes which are doing something similar to what you do, so I don't say this because I do not like SQLCLR).
In your case, I would look at either use Change Notifications, or as you mention in your post - Service Broker. Be aware that with SSB you can run into performance issues (latches, locks, etc.) if your system is highly volatile (+2,000 tx/sec). At least that is what we experienced.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53994252%2fissue-in-asynchronous-api-calls-from-sqlclr%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
There are a couple of things that stand out as possible issues:
- Wouldn't the call to
allDone.WaitOne();
block until a response is received anyway, negating the need / use of all this async stuff? - Even if this does work, are you testing this in a single session? You have several static member (class-level) variables, such as
public static ManualResetEvent allDone
, that are shared across all sessions. SQLCLR uses a shared App Domain (App Domains are per Database / per Assembly owner). Hence multiple sessions will over-write each other's values of these shared static variables. That is very dangerous (hence why non-readonly static variables are only allowed inUNSAFE
Assemblies). This model only works if you can guarantee a single caller at any given moment.
Beyond any SQLCLR technicalities, I am not sure that this is a good model even if you do manage to get past this particular issue.
A better, safer model would be to:
- create a queue table to log these changes. you typically only need the key columns and a timestamp (
DATETIME
orDATETIME2
, not theTIMESTAMP
datatype). - have the trigger log the current time and rows modified into the queue table
create a stored procedure that takes items from the queue, starting with the oldest records, processes them (which can definitely be calling your SQLCLR stored procedure to do the web server call, but no need for it to be async, so remove that stuff and set the Assembly back to
EXTERNAL_ACCESS
since you don't need/wantUNSAFE
).
Do this in a transaction so that the records are not fully removed from the queue table if the "processing" fails. Sometimes using the
OUTPUT
clause withDELETE
to reserve rows that you are working on into a local temp table helps.
Process multiple records at a time, even if calling the SQLCLR stored procedure needs to be done on a per-row basis.
- create a SQL Server agent job to execute the stored procedure every minute (or less depending on need)
Minor issues:
- the pattern of copying input parameters into static variables (e.g.
_mailServer = mailServer;
) is pointless at best, and error-prone regardless due to not being thread-safe. remember, static variables are shared across all sessions, so any concurrent sessions will be overwriting the previous values, hence ensure race conditions. Please remove all of the variables with names starting with an underscore. - the pattern of using
Convert.To...
is also unnecessary and a slight hit to performance. AllSql*
types have aValue
property that returns the expected .NET type. Hence, you only need:string url = weburl.Value;
- there is no need to use incorrect datatypes that require conversion later on. Meaning, rather than using
SqlString
forconnectionLimit
, instead useSqlInt32
and then you can simply doint connLimit = connectionLimit.Value;
- You probably don't need to do the security manually (i.e.
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
). I am pretty sure you can just create a new NetworkCredential usinguname
andpass
and assign that to the request.
In SQL CLR AppDomains are not long-lived. Your AppDomain may be unloaded or recycled at any time, and in this model SQL Server won't wait for your HTTP calls to complete before shutting it down. So this won't be reliable. Also to get rid of the static variables, capture the local variables to pass to the callback.
– David Browne - Microsoft
Jan 2 at 16:48
@DavidBrowne-Microsoft Good points, thanks. I was about to add the first one to my answer but then remembered that App Domains are not unloaded immediately: they are marked as "doomed" and do allow for current running threads to complete, while new requests will spin up a new App Domain. In this case, both App Domains can exist concurrently, though they cannot see each other and there is no way to access the "doomed" App Domain. Only caveat here is that I haven't tested with async requests, but I don't believe that the behavior would be any different.
– Solomon Rutzky
Jan 2 at 16:59
I don't think SQL knows about the pending async request, as no thread is waiting on the response. It may, somehow, but it's not documented or simple to test.
– David Browne - Microsoft
Jan 2 at 17:05
add a comment |
There are a couple of things that stand out as possible issues:
- Wouldn't the call to
allDone.WaitOne();
block until a response is received anyway, negating the need / use of all this async stuff? - Even if this does work, are you testing this in a single session? You have several static member (class-level) variables, such as
public static ManualResetEvent allDone
, that are shared across all sessions. SQLCLR uses a shared App Domain (App Domains are per Database / per Assembly owner). Hence multiple sessions will over-write each other's values of these shared static variables. That is very dangerous (hence why non-readonly static variables are only allowed inUNSAFE
Assemblies). This model only works if you can guarantee a single caller at any given moment.
Beyond any SQLCLR technicalities, I am not sure that this is a good model even if you do manage to get past this particular issue.
A better, safer model would be to:
- create a queue table to log these changes. you typically only need the key columns and a timestamp (
DATETIME
orDATETIME2
, not theTIMESTAMP
datatype). - have the trigger log the current time and rows modified into the queue table
create a stored procedure that takes items from the queue, starting with the oldest records, processes them (which can definitely be calling your SQLCLR stored procedure to do the web server call, but no need for it to be async, so remove that stuff and set the Assembly back to
EXTERNAL_ACCESS
since you don't need/wantUNSAFE
).
Do this in a transaction so that the records are not fully removed from the queue table if the "processing" fails. Sometimes using the
OUTPUT
clause withDELETE
to reserve rows that you are working on into a local temp table helps.
Process multiple records at a time, even if calling the SQLCLR stored procedure needs to be done on a per-row basis.
- create a SQL Server agent job to execute the stored procedure every minute (or less depending on need)
Minor issues:
- the pattern of copying input parameters into static variables (e.g.
_mailServer = mailServer;
) is pointless at best, and error-prone regardless due to not being thread-safe. remember, static variables are shared across all sessions, so any concurrent sessions will be overwriting the previous values, hence ensure race conditions. Please remove all of the variables with names starting with an underscore. - the pattern of using
Convert.To...
is also unnecessary and a slight hit to performance. AllSql*
types have aValue
property that returns the expected .NET type. Hence, you only need:string url = weburl.Value;
- there is no need to use incorrect datatypes that require conversion later on. Meaning, rather than using
SqlString
forconnectionLimit
, instead useSqlInt32
and then you can simply doint connLimit = connectionLimit.Value;
- You probably don't need to do the security manually (i.e.
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
). I am pretty sure you can just create a new NetworkCredential usinguname
andpass
and assign that to the request.
In SQL CLR AppDomains are not long-lived. Your AppDomain may be unloaded or recycled at any time, and in this model SQL Server won't wait for your HTTP calls to complete before shutting it down. So this won't be reliable. Also to get rid of the static variables, capture the local variables to pass to the callback.
– David Browne - Microsoft
Jan 2 at 16:48
@DavidBrowne-Microsoft Good points, thanks. I was about to add the first one to my answer but then remembered that App Domains are not unloaded immediately: they are marked as "doomed" and do allow for current running threads to complete, while new requests will spin up a new App Domain. In this case, both App Domains can exist concurrently, though they cannot see each other and there is no way to access the "doomed" App Domain. Only caveat here is that I haven't tested with async requests, but I don't believe that the behavior would be any different.
– Solomon Rutzky
Jan 2 at 16:59
I don't think SQL knows about the pending async request, as no thread is waiting on the response. It may, somehow, but it's not documented or simple to test.
– David Browne - Microsoft
Jan 2 at 17:05
add a comment |
There are a couple of things that stand out as possible issues:
- Wouldn't the call to
allDone.WaitOne();
block until a response is received anyway, negating the need / use of all this async stuff? - Even if this does work, are you testing this in a single session? You have several static member (class-level) variables, such as
public static ManualResetEvent allDone
, that are shared across all sessions. SQLCLR uses a shared App Domain (App Domains are per Database / per Assembly owner). Hence multiple sessions will over-write each other's values of these shared static variables. That is very dangerous (hence why non-readonly static variables are only allowed inUNSAFE
Assemblies). This model only works if you can guarantee a single caller at any given moment.
Beyond any SQLCLR technicalities, I am not sure that this is a good model even if you do manage to get past this particular issue.
A better, safer model would be to:
- create a queue table to log these changes. you typically only need the key columns and a timestamp (
DATETIME
orDATETIME2
, not theTIMESTAMP
datatype). - have the trigger log the current time and rows modified into the queue table
create a stored procedure that takes items from the queue, starting with the oldest records, processes them (which can definitely be calling your SQLCLR stored procedure to do the web server call, but no need for it to be async, so remove that stuff and set the Assembly back to
EXTERNAL_ACCESS
since you don't need/wantUNSAFE
).
Do this in a transaction so that the records are not fully removed from the queue table if the "processing" fails. Sometimes using the
OUTPUT
clause withDELETE
to reserve rows that you are working on into a local temp table helps.
Process multiple records at a time, even if calling the SQLCLR stored procedure needs to be done on a per-row basis.
- create a SQL Server agent job to execute the stored procedure every minute (or less depending on need)
Minor issues:
- the pattern of copying input parameters into static variables (e.g.
_mailServer = mailServer;
) is pointless at best, and error-prone regardless due to not being thread-safe. remember, static variables are shared across all sessions, so any concurrent sessions will be overwriting the previous values, hence ensure race conditions. Please remove all of the variables with names starting with an underscore. - the pattern of using
Convert.To...
is also unnecessary and a slight hit to performance. AllSql*
types have aValue
property that returns the expected .NET type. Hence, you only need:string url = weburl.Value;
- there is no need to use incorrect datatypes that require conversion later on. Meaning, rather than using
SqlString
forconnectionLimit
, instead useSqlInt32
and then you can simply doint connLimit = connectionLimit.Value;
- You probably don't need to do the security manually (i.e.
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
). I am pretty sure you can just create a new NetworkCredential usinguname
andpass
and assign that to the request.
There are a couple of things that stand out as possible issues:
- Wouldn't the call to
allDone.WaitOne();
block until a response is received anyway, negating the need / use of all this async stuff? - Even if this does work, are you testing this in a single session? You have several static member (class-level) variables, such as
public static ManualResetEvent allDone
, that are shared across all sessions. SQLCLR uses a shared App Domain (App Domains are per Database / per Assembly owner). Hence multiple sessions will over-write each other's values of these shared static variables. That is very dangerous (hence why non-readonly static variables are only allowed inUNSAFE
Assemblies). This model only works if you can guarantee a single caller at any given moment.
Beyond any SQLCLR technicalities, I am not sure that this is a good model even if you do manage to get past this particular issue.
A better, safer model would be to:
- create a queue table to log these changes. you typically only need the key columns and a timestamp (
DATETIME
orDATETIME2
, not theTIMESTAMP
datatype). - have the trigger log the current time and rows modified into the queue table
create a stored procedure that takes items from the queue, starting with the oldest records, processes them (which can definitely be calling your SQLCLR stored procedure to do the web server call, but no need for it to be async, so remove that stuff and set the Assembly back to
EXTERNAL_ACCESS
since you don't need/wantUNSAFE
).
Do this in a transaction so that the records are not fully removed from the queue table if the "processing" fails. Sometimes using the
OUTPUT
clause withDELETE
to reserve rows that you are working on into a local temp table helps.
Process multiple records at a time, even if calling the SQLCLR stored procedure needs to be done on a per-row basis.
- create a SQL Server agent job to execute the stored procedure every minute (or less depending on need)
Minor issues:
- the pattern of copying input parameters into static variables (e.g.
_mailServer = mailServer;
) is pointless at best, and error-prone regardless due to not being thread-safe. remember, static variables are shared across all sessions, so any concurrent sessions will be overwriting the previous values, hence ensure race conditions. Please remove all of the variables with names starting with an underscore. - the pattern of using
Convert.To...
is also unnecessary and a slight hit to performance. AllSql*
types have aValue
property that returns the expected .NET type. Hence, you only need:string url = weburl.Value;
- there is no need to use incorrect datatypes that require conversion later on. Meaning, rather than using
SqlString
forconnectionLimit
, instead useSqlInt32
and then you can simply doint connLimit = connectionLimit.Value;
- You probably don't need to do the security manually (i.e.
httpWebRequest.Headers.Add("Authorization", "Basic " + encoded);
). I am pretty sure you can just create a new NetworkCredential usinguname
andpass
and assign that to the request.
edited Jan 2 at 16:51
answered Jan 2 at 16:28
Solomon RutzkySolomon Rutzky
32.5k477116
32.5k477116
In SQL CLR AppDomains are not long-lived. Your AppDomain may be unloaded or recycled at any time, and in this model SQL Server won't wait for your HTTP calls to complete before shutting it down. So this won't be reliable. Also to get rid of the static variables, capture the local variables to pass to the callback.
– David Browne - Microsoft
Jan 2 at 16:48
@DavidBrowne-Microsoft Good points, thanks. I was about to add the first one to my answer but then remembered that App Domains are not unloaded immediately: they are marked as "doomed" and do allow for current running threads to complete, while new requests will spin up a new App Domain. In this case, both App Domains can exist concurrently, though they cannot see each other and there is no way to access the "doomed" App Domain. Only caveat here is that I haven't tested with async requests, but I don't believe that the behavior would be any different.
– Solomon Rutzky
Jan 2 at 16:59
I don't think SQL knows about the pending async request, as no thread is waiting on the response. It may, somehow, but it's not documented or simple to test.
– David Browne - Microsoft
Jan 2 at 17:05
add a comment |
In SQL CLR AppDomains are not long-lived. Your AppDomain may be unloaded or recycled at any time, and in this model SQL Server won't wait for your HTTP calls to complete before shutting it down. So this won't be reliable. Also to get rid of the static variables, capture the local variables to pass to the callback.
– David Browne - Microsoft
Jan 2 at 16:48
@DavidBrowne-Microsoft Good points, thanks. I was about to add the first one to my answer but then remembered that App Domains are not unloaded immediately: they are marked as "doomed" and do allow for current running threads to complete, while new requests will spin up a new App Domain. In this case, both App Domains can exist concurrently, though they cannot see each other and there is no way to access the "doomed" App Domain. Only caveat here is that I haven't tested with async requests, but I don't believe that the behavior would be any different.
– Solomon Rutzky
Jan 2 at 16:59
I don't think SQL knows about the pending async request, as no thread is waiting on the response. It may, somehow, but it's not documented or simple to test.
– David Browne - Microsoft
Jan 2 at 17:05
In SQL CLR AppDomains are not long-lived. Your AppDomain may be unloaded or recycled at any time, and in this model SQL Server won't wait for your HTTP calls to complete before shutting it down. So this won't be reliable. Also to get rid of the static variables, capture the local variables to pass to the callback.
– David Browne - Microsoft
Jan 2 at 16:48
In SQL CLR AppDomains are not long-lived. Your AppDomain may be unloaded or recycled at any time, and in this model SQL Server won't wait for your HTTP calls to complete before shutting it down. So this won't be reliable. Also to get rid of the static variables, capture the local variables to pass to the callback.
– David Browne - Microsoft
Jan 2 at 16:48
@DavidBrowne-Microsoft Good points, thanks. I was about to add the first one to my answer but then remembered that App Domains are not unloaded immediately: they are marked as "doomed" and do allow for current running threads to complete, while new requests will spin up a new App Domain. In this case, both App Domains can exist concurrently, though they cannot see each other and there is no way to access the "doomed" App Domain. Only caveat here is that I haven't tested with async requests, but I don't believe that the behavior would be any different.
– Solomon Rutzky
Jan 2 at 16:59
@DavidBrowne-Microsoft Good points, thanks. I was about to add the first one to my answer but then remembered that App Domains are not unloaded immediately: they are marked as "doomed" and do allow for current running threads to complete, while new requests will spin up a new App Domain. In this case, both App Domains can exist concurrently, though they cannot see each other and there is no way to access the "doomed" App Domain. Only caveat here is that I haven't tested with async requests, but I don't believe that the behavior would be any different.
– Solomon Rutzky
Jan 2 at 16:59
I don't think SQL knows about the pending async request, as no thread is waiting on the response. It may, somehow, but it's not documented or simple to test.
– David Browne - Microsoft
Jan 2 at 17:05
I don't think SQL knows about the pending async request, as no thread is waiting on the response. It may, somehow, but it's not documented or simple to test.
– David Browne - Microsoft
Jan 2 at 17:05
add a comment |
Hi there (and Happy New Year) :)!
A couple of things:
If you can, stay away from triggers! They can cause a lot of unexpected side effects, especially if you use them for business logic. What I mean with that, is that they are good for auditing purposes, but apart from that I try and avoid them like the plague.
So, if you do not use triggers, how do you know when something is inserted? Well, I hope that your inserts happen through stored procedures, and are not direct inserts in the table(s). If you use stored procedures you can have a procedure which is doing your logic, and that procedure is called from the procedure which does the insert.
Back to your question. I don't really have an answer to the specifics, but I would stay away from using SQLCLR in this case (sidenote: I am a BIG proponent of SQLCLR, and I have implemented a lot of SQLCLR processes which are doing something similar to what you do, so I don't say this because I do not like SQLCLR).
In your case, I would look at either use Change Notifications, or as you mention in your post - Service Broker. Be aware that with SSB you can run into performance issues (latches, locks, etc.) if your system is highly volatile (+2,000 tx/sec). At least that is what we experienced.
add a comment |
Hi there (and Happy New Year) :)!
A couple of things:
If you can, stay away from triggers! They can cause a lot of unexpected side effects, especially if you use them for business logic. What I mean with that, is that they are good for auditing purposes, but apart from that I try and avoid them like the plague.
So, if you do not use triggers, how do you know when something is inserted? Well, I hope that your inserts happen through stored procedures, and are not direct inserts in the table(s). If you use stored procedures you can have a procedure which is doing your logic, and that procedure is called from the procedure which does the insert.
Back to your question. I don't really have an answer to the specifics, but I would stay away from using SQLCLR in this case (sidenote: I am a BIG proponent of SQLCLR, and I have implemented a lot of SQLCLR processes which are doing something similar to what you do, so I don't say this because I do not like SQLCLR).
In your case, I would look at either use Change Notifications, or as you mention in your post - Service Broker. Be aware that with SSB you can run into performance issues (latches, locks, etc.) if your system is highly volatile (+2,000 tx/sec). At least that is what we experienced.
add a comment |
Hi there (and Happy New Year) :)!
A couple of things:
If you can, stay away from triggers! They can cause a lot of unexpected side effects, especially if you use them for business logic. What I mean with that, is that they are good for auditing purposes, but apart from that I try and avoid them like the plague.
So, if you do not use triggers, how do you know when something is inserted? Well, I hope that your inserts happen through stored procedures, and are not direct inserts in the table(s). If you use stored procedures you can have a procedure which is doing your logic, and that procedure is called from the procedure which does the insert.
Back to your question. I don't really have an answer to the specifics, but I would stay away from using SQLCLR in this case (sidenote: I am a BIG proponent of SQLCLR, and I have implemented a lot of SQLCLR processes which are doing something similar to what you do, so I don't say this because I do not like SQLCLR).
In your case, I would look at either use Change Notifications, or as you mention in your post - Service Broker. Be aware that with SSB you can run into performance issues (latches, locks, etc.) if your system is highly volatile (+2,000 tx/sec). At least that is what we experienced.
Hi there (and Happy New Year) :)!
A couple of things:
If you can, stay away from triggers! They can cause a lot of unexpected side effects, especially if you use them for business logic. What I mean with that, is that they are good for auditing purposes, but apart from that I try and avoid them like the plague.
So, if you do not use triggers, how do you know when something is inserted? Well, I hope that your inserts happen through stored procedures, and are not direct inserts in the table(s). If you use stored procedures you can have a procedure which is doing your logic, and that procedure is called from the procedure which does the insert.
Back to your question. I don't really have an answer to the specifics, but I would stay away from using SQLCLR in this case (sidenote: I am a BIG proponent of SQLCLR, and I have implemented a lot of SQLCLR processes which are doing something similar to what you do, so I don't say this because I do not like SQLCLR).
In your case, I would look at either use Change Notifications, or as you mention in your post - Service Broker. Be aware that with SSB you can run into performance issues (latches, locks, etc.) if your system is highly volatile (+2,000 tx/sec). At least that is what we experienced.
answered Jan 2 at 4:28
Niels BerglundNiels Berglund
1,07036
1,07036
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53994252%2fissue-in-asynchronous-api-calls-from-sqlclr%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
As far as I understand your service also tries to read that newly added data from the same table to return the result. You can avoid this situation by copying that newly added data to another table like "table1_new" or change your workflow to support eventual consistency instead of ACID. If you use eventual consistency you can send this message to a message bus and do not wait for the result. You can also use a database change tracker (like linkedin Databus) to track changes in your tables without touching to your actual database.
– NthDeveloper
Jan 1 at 14:41