06.06.2022, 12:40 | #1 |
Участник
|
DAX 2012. Как споймать ошибку try catch внутри транзакции?
Всем привет!
У меня при обновлении таблицы идёт запрос WebRequest к внешней службе, чтобы и там обновить данные X++: public void update() { ttsBegin; super(); this.MyRequest(this); ttsCommit; Функция имеет вид наподобии такого: X++: str responseString; System.Net.WebRequest webRequest; System.Net.HttpWebResponse httpResponse; CLRObject responseObj; System.IO.Stream stream; System.IO.StreamReader streamReader; System.Exception ex; System.Net.WebException webException; ; try { if (!urlAPI) throw error("Error"); codeAccessPermission::revertAssert(); new InteropPermission(InteropKind::ClrInterop).assert(); webRequest = System.Net.WebRequest::Create(urlAPI); webRequest.set_Method('POST'); stream = webRequest.GetRequestStream(); stream.Write(arrayOfBytes,0,arrayOfBytes.get_Length()); stream.Close (); httpResponse = webRequest.GetResponse(); } catch (Exception::CLRError) { ex = ClrInterop::getLastException(); if (ex != null) { ex = ex.get_InnerException(); if ((ex != null) && (ex is System.Net.WebException)) { webException = ex as System.Net.WebException; responseObj = webException.get_Response(); httpResponse = responseObj as System.Net.HttpWebResponse; } } } catch { error("Error"); return ''; } stream = httpResponse.GetResponseStream(); streamReader = new System.IO.StreamReader(stream); responseString = streamReader.ReadToEnd(); streamReader.Close(); stream.Close(); httpResponse.Close(); codeAccessPermission::revertAssert(); return responseString; |
|
06.06.2022, 13:03 | #2 |
Участник
|
Может сделать запрос реквеста вне транзакции?
И уж если он отработал то что то делать |
|
06.06.2022, 13:21 | #3 |
Administrator
|
"В лоб" нет ответа. Классический аналог задачи - в транзакцию запихнуть отправку на электронную почту письма. При этом на вопрос "откатываем ли транзакцию, если почтовый сервер стал недоступен?" обычно отвечают, что "ну мы же не можем останавливать работу системы из-за того, что где-то какой-то сервер, которым мы не управляем - стал недоступен"
Поэтому ответ здесь зависит от ситуации. Что вероятнее может случиться - ошибка и откат транзакции или ошибка при веб-запросе? Вариант 1. Ошибка и откат транзакции. В этом случае: 1. Делаем проверку на то, что ошибки не будет. Для этого - запускаем на исполнение этот же код, но в конце вызываем ttsabort. Все выборки на обновление делаем с пессимистической блокировкой для гарантии исключения конфликтов совместного обновления. Если код дошёл до конца - то вызываем ttsabort. 2. (Транзакция не открыта). Вызываем веб-запрос. Если вернулась ошибка - не вызываем код с транзакцией. Если ошибки нет - то вызываем код с транзакцией, пессимистическими блокировками и уже делаем ttscommit; Вариант 2. Ошибка при веб-запросе. Здесь надо "на берегу" договориться - останавливаем ли мы работу системы до тех пор, пока веб-запрос не перестанет возвращать ошибки (пример с почтовым сервером) или идем дальше, а с веб-запросом будем разбираться отдельно? Если останавливаем - то тогда отрабатываем по варианту 1. Если не останавливаем, то тогда сначала вызываем код в транзакции и только после его успешной отработки - вызываем веб-запрос. Если веб-запрос выдает ошибки - то эти ошибки логируем и готовим какой-нибудь пакетник по массовой обработки накопившихся ошибок (пример - массовая рассылка почты по тем письмам, которые не были отправлены). Ну собственно - здесь основной вопрос и есть - что делать системе, если по той или иной причине не получается отработать веб-запрос
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 06.06.2022 в 13:23. |
|
06.06.2022, 13:26 | #4 |
Участник
|
Добрый день.
Транзакцию можно обернуть внутри иного user connection'a. |
|
06.06.2022, 18:59 | #5 |
Участник
|
Коллега (glibs) подсказал как-то хороший трюк для таких случаев.
Можно ваш код вынести в отдельный метод и дернуть его через runAs тогда он отработает в отдельной X++ сессии в которой будет своей соединение к БД. И если будет исключение то его можно будет обработать - исходная ваша транзакция не откатится. Но надо аккуратно быть с обновлениями - можно в блокировку попасть. т.е. нежелательно чтобы этот ваш метод что-то обновлял. |
|
|
За это сообщение автора поблагодарили: dim-gin (1), sukhanchik (4). |
06.06.2022, 19:49 | #6 |
Участник
|
Раз уж зашла речь про транзакции и откаты, можно немного оффтопом задать вопрос: влияет ли установленный через Connection XACT_ABORT = ON (и не возвращённый в OFF) на работу клиента аксапты?
|
|
07.06.2022, 00:23 | #7 |
Участник
|
Можно просто вынести это в C# библотеку и там обрабатывайте все исключения, что намного удобнее чем в х++
|
|
08.06.2022, 09:48 | #8 |
Участник
|
Я сделал axapta-vs-c# проект WebRequestNet с оберткой:
X++: using System; using System.Net; namespace WebRequestNet { public class HttpWebRequestExceptionSafe { public HttpWebRequest HttpWebRequest { get; private set; } protected HttpWebRequestExceptionSafe(HttpWebRequest _httpWebRequest) { this.HttpWebRequest = _httpWebRequest; } public static HttpWebRequestExceptionSafe Create(Uri requestUri) { return new HttpWebRequestExceptionSafe((HttpWebRequest)HttpWebRequest.Create(requestUri)); } public static HttpWebRequestExceptionSafe Create(string requestUriString) { return new HttpWebRequestExceptionSafe((HttpWebRequest)HttpWebRequest.Create(requestUriString)); } public HttpWebResponse GetResponseSafe() { try { return this.HttpWebRequest.GetResponse() as HttpWebResponse; } catch (WebException ex) { HttpWebResponse response = ex.Response as System.Net.HttpWebResponse; if (response == null) throw; return response; } } } } X++: /// <summary> /// Get retail web access request. /// </summary> /// <param name="_request"> /// Retail web request. /// </param> /// <returns> /// Web response to access. /// </returns> public RetailWebResponse getResponse(RetailWebRequest _request) { System.Net.HttpWebRequest request = null; System.Net.HttpWebResponse response = null; CLRObject webResponse; System.Net.WebHeaderCollection headers; System.IO.MemoryStream stream; Binary requestContent; str responseData; System.Exception ex; System.Net.WebException webException; RetailWebResponse retailWebResponse; int httpStatusCode; str contentType; MapEnumerator headerMapEnumerator; //+ Abramov_ 26.04.2022 TSK0000262_02 WebRequestNet.HttpWebRequestExceptionSafe requestAdapter; str exceptionMessage; System.Exception innerException; System.Net.WebExceptionStatus webExceptionStatus; //- Abramov_ 26.04.2022 TSK0000262_02 ; try { //+ Abramov_ 20.04.2022 TSK0000262_02 //request = System.Net.WebRequest::Create(_request.parmUrl()) as System.Net.HttpWebRequest; requestAdapter = WebRequestNet.HttpWebRequestExceptionSafe::Create(_request.parmUrl()); request = requestAdapter.get_HttpWebRequest(); //- Abramov_ 20.04.2022 TSK0000262_02 if (strLen(_request.parmMethod()) > 0) { request.set_Method(_request.parmMethod()); } headers = request.get_Headers(); //+ Abramov_ 19.11.2021 TSK0000009_01 /* if (strLen(_request.parmHeader()) > 0) { headers.Add(_request.parmHeader()); } */ headerMapEnumerator = _request.getHeaders().getEnumerator(); while (headerMapEnumerator.moveNext()) { headers.Add(any2str(headerMapEnumerator.currentKey()), any2str(headerMapEnumerator.currentValue())); } //- Abramov_ 19.11.2021 TSK0000009_01 if (strLen(_request.parmContentType()) > 0) { request.set_ContentType(_request.parmContentType()); } requestContent = _request.parmContent(); if (requestContent) { stream = requestContent.getMemoryStream(); RetailCommonWebAPI::writeRequestData(request, stream.ToArray()); } //+ Abramov_ 19.11.2021 TSK0000009_01 if (_request.parmKeepAlive()) request.set_KeepAlive(CLRInterop::getObjectForAnyType(_request.parmKeepAlive())); //- Abramov_ 19.11.2021 TSK0000009_01 //+ Abramov_ 25.04.2022 TSK0000264_01 if (_request.parmTimeout()) request.set_Timeout(_request.parmTimeout()); //- Abramov_ 25.04.2022 TSK0000264_01 //+ Abramov_ 20.04.2022 TSK0000262_02 // Вызов "безопасной" версии метода GetResponse, который не генерирует исключение при получении ответа от сервера, но с кодом отличным от 200. /* webResponse = request.GetResponse(); response = webResponse as System.Net.HttpWebResponse; */ response = requestAdapter.GetResponseSafe(); //- Abramov_ 20.04.2022 TSK0000262_02 } catch (Exception::CLRError) { ex = ClrInterop::getLastException(); if (ex != null && ex.get_InnerException() != null) { innerException = ex.get_InnerException(); if (innerException is System.Net.WebException) { webException = innerException as System.Net.WebException; //+ Abramov_ 26.04.2022 TSK0000262_02 //Обработка случая, когда Response "сидит" в WebException, находится в методе requestAdapter.GetResponseSafe(); webExceptionStatus = webException.get_Status(); if (webExceptionStatus == System.Net.WebExceptionStatus::Timeout) { error(innerException.get_Message()); throw Exception::Timeout; } else { throw error(innerException.get_Message()); } //- Abramov_ 26.04.2022 TSK0000262_02 } } //+ Abramov_ 26.04.2022 TSK0000262_02 innerException = ex.get_InnerException(); while (innerException) { // BP deviation documented if (exceptionMessage) exceptionMessage += '\n'; exceptionMessage += exceptionMessage + " " + CLRInterop::getAnyTypeForObject(innerException.get_Message()); innerException = innerException.get_InnerException(); } throw error(exceptionMessage); //- Abramov_ 26.04.2022 TSK0000262_02 } responseData = RetailCommonWebAPI::readResponseData(response); response.Close(); httpStatusCode = response.get_StatusCode(); contentType = response.get_ContentType(); retailWebResponse = new RetailWebResponse(httpStatusCode, responseData, contentType); return retailWebResponse; } |
|
11.06.2022, 19:16 | #9 |
Участник
|
X++: // to write your log from inside of another transaction static public void insertExtEventLogInSeparateConnection(RefRecId _lineRecId, str _guid, str _logSource, str _logStr) { ExtEventLog log; UserConnection connection; int ttsLevel = appl.ttsLevel(); //here you can check if you are inside of a transaction str errMsg = strFmt("Cannot add event log '%1:%2:%3'", _guid, _logSource, _logStr); // let's create a separate connection try { connection = new UserConnection(); connection.ttsbegin(); log.setConnection(connection); log.InstructionDocLineRecId = _lineRecId; log.TaskGUID = _guid; log.LogStr = _logStr; log.LogSource = _logSource; log.doInsert(); connection.ttscommit(); } catch { throw error(errMsg); } finally { if(connection) { connection.finalize(); } } }
__________________
Felix nihil admirari |
|