我遇到了奇怪的错误SQL 2012
and CLR
:
在进入用户定义的例程、触发器或聚合“我的例程”之前处于活动状态的上下文事务已在其中结束,这是不允许的。更改应用程序逻辑以强制执行严格的事务嵌套。
我不知道可能是什么问题。谁能给我一些搜索的方向?
一般来说,我有 SQL 存储过程,在UPDATE
子句中我调用scalar CLR function
. 该函数获取 2 个xml
字符串:一个用于验证xml
,另一个用于xsd
验证。该函数在查询中被多次调用。
编辑:
错误信息:
错误号:3991
错误严重性:16
错误状态:1
错误过程:sproc_XXX
错误行:297
错误消息:在进入用户定义的例程、触发器或聚合“CLR_XXX_Xml”之前处于活动状态的上下文事务已在其中结束,这是不允许的。更改应用程序逻辑以强制执行严格的事务嵌套。
编辑2:
我发现了问题。当我们使用CLR
validateXML
时,我们将第一个参数设置为XSD
名称。然后在我们从 SQL Server中CLR
提取模式。XSD
在我们的例子中,我们有空字符串作为XSD
名称,但即使我们有try / catch
错误也向上传递到 SQL Server 存储过程。
我们的函数返回0 / 1
,因此必须使用错误代码更新带有 0 的记录。
但在这种情况下,我们在 SQL 中收到异常。
我的代码是:
用于更新的 SQL
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);
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;
}
}
}
“在进入用户定义的例程、触发器或聚合之前处于活动状态的上下文事务“...”已在其中结束,这是不允许的。 ”错误的直接原因是:
您正在调用XML_SCHEMA_NAMESPACE()内置函数,有时由于找不到指定的 XML 模式集合而失败。根据问题中的信息,当发生此错误时,您将传入一个空字符串,但传入与现有 XML 模式集合不匹配的任何字符串都会导致此错误。
发生的情况是,当调用此 SQLCLR 函数(即
UPDATE
语句的事务)时,您当前处于事务中,并且失败XML_SCHEMA_NAMESPACE
是批处理中止错误,在这种情况下事务会自动回滚(因此try...catch
在.NET 代码在这里无能为力)。如果您有嵌套的存储过程调用,其中外部存储过程开始事务并且内部存储过程发出 aROLLBACK
在这种情况@@TRANCOUNT
下,退出内部存储过程时的值将低于它时,这实际上是相同的错误输入它,你会得到一个错误,指出 的起始值和结束值@@TRANCOUNT
不一样。这是显示
XML_SCHEMA_NAMESPACE
失败是批处理(因此是事务)中止错误的示例代码:回报:
此错误的根本原因是:
您允许将值传递给
XML_SCHEMA_NAMESPACE
导致其出错的函数,特别是空字符串。此错误的修复方法是执行以下操作:
NULL
仅包含空格的字符串值。这将防止由于NULL
值(无论如何都会导致当前代码出错)以及由空格、制表符、回车等的任意组合填充的非空字符串引起的错误(该IsNullOrWhiteSpace
方法检查所有 Unicode 空白字符)。将:替换if (XsdSchemaString.ToString().Length == 0)
为:
if (XsdSchemaString.IsNull || String.IsNullOrWhiteSpace(XsdSchemaString.Value))
在调用之前检查传入的 XML Schema Collection 名称是否存在,
XML_SCHEMA_NAMESPACE
以便仅在名称实际存在时才调用该函数。您可以在与当前相同的步骤中执行此操作,方法是将其SELECT
包装在IF EXISTS
:这将防止由于不正确的非空、非仅空白值而导致的错误。不正确的值不会返回结果集,并且您的代码已经将其
else
作为if (reader.Read())
.