Estou experimentando um erro estranho com SQL 2012
e CLR
:
A transação de contexto que estava ativa antes de entrar na rotina definida pelo usuário, disparar ou agregar "Minha rotina" foi finalizada dentro dela, o que não é permitido. Altere a lógica do aplicativo para impor o aninhamento estrito da transação.
Não tenho ideia do que pode ser o problema. Alguém pode me dar alguma direção onde procurar?
Em geral, tenho um procedimento armazenado SQL dentro do qual, na UPDATE
cláusula, chamo scalar CLR function
. A função recebe 2 xml
strings: uma para xml
e outra para xsd
uma validação de make. A função é chamada várias vezes na consulta.
EDITAR:
Mensagem de erro:
Número do erro: 3991
Gravidade do erro: 16
Estado de erro: 1
Procedimento de erro: sproc_XXX
Linha de erro: 297
Mensagem de erro: A transação de contexto que estava ativa antes de entrar na rotina definida pelo usuário, gatilho ou agregado "CLR_XXX_Xml" foi finalizada dentro dela, o que não é permitido. Altere a lógica do aplicativo para impor o aninhamento estrito da transação.
EDIÇÃO 2:
Eu acho o problema. Quando usamos CLR
para validar XML
, sned 1º parâmetro para ser o XSD
nome. Em seguida CLR
, extraímos o XSD
esquema do SQL Server. No nosso caso, temos uma string vazia para o XSD
nome, mas mesmo assim try / catch
o erro é passado para o procedimento armazenado do SQL Server.
Nossa função retorna 0 / 1
então os registros com 0 devem ser atualizados com código de erro.
Mas neste caso recebemos exceção no SQL.
Meu código é:
SQL para atualização
UPDATE FR
SET ErrorCode = 'OUR ERROR CODE'
FROM dbo.DataTable FR
INNER JOIN dbo.TempTable FRN ON (FRN.SessionGUID = @sSessionGUID)
AND (FRN.IDLog = FR.IDLog)
AND (FRN.UniqueID = FR.UniqueID)
AND (
(FRN.Template LIKE '%##%')
OR (PANI_Core.dbo.funCLRs_CheckXML('schRPT_' + @sSchema + '_Peliminary',
FRN.Template) <> 1)
OR (NULLIF(FRN.Template, '') IS NULL)
OR (
(TRY_CONVERT(DATE, FRN.ExpirationDate) IS NULL)
AND (NULLIF(FRN.ExpirationDate, '') IS NOT NULL)
)
OR (
(TRY_CONVERT(VARCHAR(35), FRN.EGN) IS NULL)
AND (NULLIF(FRN.EGN, '') IS NOT NULL)
)
OR (NULLIF(FRN.EGN, '') IS NULL)
OR (
(TRY_CONVERT(MONEY, FRN.Amount) IS NULL)
AND (NULLIF(FRN.Amount, '') IS NOT NULL)
)
OR (NULLIF(FRN.Amount, '') IS NULL)
OR (
(TRY_CONVERT(VARCHAR(32), FRN.UniqueClientID) IS NULL)
AND (NULLIF(FRN.UniqueCID, '') IS NOT NULL)
)
OR (NULLIF(FRN.UniqueCID, '') IS NULL)
)
WHERE (FR.IDLog = @IDLog)
AND (FR.ErrorCode IS NULL);
Código CLR:
public static SqlBoolean funCLRs_CheckXml(SqlString XsdSchemaString, SqlString ValueString)
{
XmlDocument asset1 = new XmlDocument();
XmlSchema schema1 = new XmlSchema();
System.Text.StringBuilder o = new System.Text.StringBuilder();
using (SqlConnection sqlLocalConn = new SqlConnection("context connection=true;"))
{
try
{
if (XsdSchemaString.ToString().Length == 0)
{
try
{
asset1.LoadXml(ValueString.ToString());
asset1 = null;
return new SqlBoolean(true);
}
catch (Exception ex)
{
return new SqlBoolean(false);
}
}
else
{
sqlLocalConn.Open();
SqlCommand cmd = new SqlCommand("SELECT XML_SCHEMA_NAMESPACE (N'dbo', N'" + XsdSchemaString.ToString() + "')", sqlLocalConn);
cmd.CommandTimeout = 240;
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
SqlXml xmlData = reader.GetSqlXml(0);
using (XmlReader xmlReader = xmlData.CreateReader())
{
schema1 = XmlSchema.Read(xmlReader, ValidateSchema);
}
asset1.Schemas.Add(schema1);
XDocument oDoc = new XDocument();
oDoc = XDocument.Parse(ValueString.ToString());
oDoc.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove();
asset1.LoadXml(oDoc.FirstNode.ToString());
oDoc = null;
asset1.Validate((o1, e) =>
{
if (e.Severity == XmlSeverityType.Error)
{
o.AppendLine("Error: " + e.Message);
}
});
if (asset1.SchemaInfo.Validity == XmlSchemaValidity.Valid)
{
asset1 = null;
return new SqlBoolean(true);
}
else
{
asset1 = null;
return new SqlBoolean(false);
}
}
else
{
asset1 = null;
return new SqlBoolean(false);
}
}
}
catch (Exception ex)
{
return new SqlBoolean(false);
}
finally
{
schema1 = null;
asset1 = null;
o = null;
}
}
}
A causa direta do erro " A transação de contexto que estava ativa antes de entrar na rotina, gatilho ou agregado definido pelo usuário"..." foi finalizada dentro dela, o que não é permitido. " é:
Você está chamando a função interna XML_SCHEMA_NAMESPACE() e, às vezes, ela falha devido a não encontrar a Coleção de Esquema XML especificada. De acordo com as informações da pergunta, você está passando uma string vazia quando esse erro ocorre, embora passar qualquer string que não corresponda a uma coleção de esquema XML existente causaria esse erro.
O que está acontecendo é que você está atualmente em uma transação quando esta função SQLCLR é chamada (ou seja, a transação da
UPDATE
instrução), e aXML_SCHEMA_NAMESPACE
falha é um erro de cancelamento de lote, caso em que a transação é revertida automaticamente (portantotry...catch
, no O código .NET não pode ajudar aqui). Este é realmente o mesmo erro que você obteria se tivesse chamadas de Procedimento Armazenado aninhadas onde o Procedimento Armazenado externo iniciou uma Transação e o Procedimento Armazenado interno emitiu umROLLBACK
, caso em que o valor de@@TRANCOUNT
seria menor ao sair do Procedimento Armazenado interno do que quando digitá-lo, e você receberia um erro informando que os valores inicial e final de@@TRANCOUNT
não eram os mesmos.Aqui está um código de exemplo mostrando que a
XML_SCHEMA_NAMESPACE
falha é um erro de cancelamento de lote (e, portanto, de transação):Retorna:
A causa raiz desse erro é:
Você está permitindo que valores sejam passados para a
XML_SCHEMA_NAMESPACE
função que causam erro, especificamente uma string vazia.A correção para esse erro é fazer o seguinte:
NULL
valores de string com espaço em branco. Isso evitará erros devido aNULL
valores (o que causaria um erro em seu código atual de qualquer maneira), bem como strings não vazias preenchidas com qualquer combinação de espaços, tabulações, retornos, etc. (oIsNullOrWhiteSpace
método verifica todos os caracteres de espaço em branco Unicode ). Substitua:if (XsdSchemaString.ToString().Length == 0)
por:
if (XsdSchemaString.IsNull || String.IsNullOrWhiteSpace(XsdSchemaString.Value))
Faça uma verificação de existência para o nome da Coleção de Esquema XML que está sendo passado, antes da chamada,
XML_SCHEMA_NAMESPACE
para que você só chame essa função se o nome realmente existir. Você pode fazer isso na mesma etapa da correnteSELECT
, envolvendo-a em umIF EXISTS
:Isso evitará erros devido a valores incorretos não vazios e sem apenas espaços em branco. Um valor incorreto não retornará um conjunto de resultados e seu código já trata isso como
else
condição paraif (reader.Read())
.