Вкратце, мне нужно асинхронно уведомлять службу веб-API от SQL Server, когда и в какой-либо таблице происходят изменения.
Для достижения вышеизложенного я создал хранимую процедуру SQLCLR, которая содержитасинхронный вызов API для уведомления службы.Хранимая процедура SQLCLR вызывается с помощью триггера по мере того, как происходит вставка в таблицу с именем Table1
.Основной проблемой здесь является то, что API должен читать данные из той же таблицы (Table1
).
Если я использую HttpWebRequest.GetResponse()
, который является синхронной версией, вся операция блокируется из-за неявной блокировкитриггера вставки.Чтобы избежать этого, я использовал метод HttpWebRequest.GetResponseAsync()
, который вызывает API и не ждет ответа.Таким образом, он запускает запрос API, и управление программой перемещается, поэтому транзакция триггера не удерживает никаких блокировок на table1
, и API смог прочитать данные из table1
.
Теперь яЯ должен реализовать механизм уведомления об ошибках, когда и когда возникают сбои (например, невозможно подключиться к удаленному серверу), и мне нужно отправить электронное письмо команде администраторовЯ написал логику составления почты внутри блока catch()
.Если я перейду к описанному выше методу HttpWebRequest.GetResponseAsync().Result
, вся операция становится синхронной, и она блокирует всю операцию.
Если я использую реализацию методов BeginGetResponse()
и EndGetResponse()
, предложенную в документах Microsoft, и запускаю SQLCLRхранимая процедура, SQL Server зависает без какой-либо информации, почему?Что я здесь не так делаю?Почему метод RespCallback()
не выполняется?
Совместное использование фрагментов кода SQLCLR, приведенных ниже.
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
}
Один из альтернативных подходов, описанных выше, - это использование SQL Server Service Broker с механизмом организации очереди, которыйпоможет нам реализовать асинхронные триггеры.Но есть ли у нас какое-либо решение для вышеуказанной ситуации?Я делаю что-то не так с точки зрения подхода?Пожалуйста, ведите меня.