1

我正在尝试使用 SQLXML 修改函数来更新多值 (xs:list) 属性。我可以在构造 XML(从字符串)时设置多个值,但 SQLXML 修改不允许我设置多个值。

初始 XML:

<AccessControlList xmlns="http://www.acme.com/Authorization/2013/01">
  <AccessControlRecord Permissions="Fullcontrol" />
  <AccessControlRecord Permissions="DenyCreate DenyRead DenyUpdate DenyDelete" />
</AccessControlList>

设置 SINGLE 值可以正常工作:

DECLARE @SingleValue NVARCHAR(100) = 'DenyCreate';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@SingleValue") cast as A:AccessPermissions ?')
FROM dbo.Widget;

设置多个值失败

DECLARE @MultipleValues NVARCHAR(100) = 'DenyCreate DenyRead DenyUpdate DenyDelete';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@MultipleValues") cast as A:AccessPermissions ?')
FROM dbo.Widget;

出现此错误:

XQuery: Replacing the value of a node with an empty sequence is allowed only if '()' is used as the new value expression. The new value expression evaluated to an empty sequence but it is not '()'.

变量不为 null 或为空。我还尝试了其他变体,但由于不同的错误而失败。

完整的 SQL 重现:

-- Drop table and schema collection
IF OBJECT_ID('dbo.Widget') IS NOT NULL
    DROP TABLE dbo.Widget;
IF EXISTS ( SELECT * FROM sys.xml_schema_collections WHERE SCHEMA_NAME(schema_id) = 'dbo' AND name = 'AccessControlList' )
    DROP XML SCHEMA COLLECTION dbo.AccessControlList;
GO

-- Create schema collection
CREATE XML SCHEMA COLLECTION dbo.AccessControlList AS N'
<xs:schema id="AccessControlList" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.acme.com/Authorization/2013/01" xmlns="http://www.acme.com/Authorization/2013/01">
    <xs:simpleType name="AccessPermissions">
        <xs:list>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="Create" />
                    <xs:enumeration value="Read" />
                    <xs:enumeration value="Update" />
                    <xs:enumeration value="Delete" />
                    <xs:enumeration value="Execute" />
                    <xs:enumeration value="Fullcontrol" />
                    <xs:enumeration value="DenyCreate" />
                    <xs:enumeration value="DenyRead" />
                    <xs:enumeration value="DenyUpdate" />
                    <xs:enumeration value="DenyDelete" />
                    <xs:enumeration value="DenyExecute" />
                    <xs:enumeration value="FullDeny" />
                </xs:restriction>
            </xs:simpleType>
        </xs:list>
    </xs:simpleType>
    <xs:complexType name="AccessControlRecord">
        <xs:attribute name="Permissions" type="AccessPermissions" use="required" />
    </xs:complexType>
    <xs:complexType name="AccessControlList">
        <xs:sequence>
            <xs:element minOccurs="0" maxOccurs="unbounded" name="AccessControlRecord" type="AccessControlRecord" />
        </xs:sequence>
    </xs:complexType>
    <xs:element name="AccessControlList" nillable="true" type="AccessControlList" />
</xs:schema>
';
GO

-- Create table, insert test data, and display initial state of data
CREATE TABLE dbo.Widget
(
    WidgetId INT PRIMARY KEY IDENTITY(1,1),
    ACL XML(DOCUMENT dbo.AccessControlList)
);
INSERT INTO dbo.Widget
    ( ACL )
VALUES
    ( N'<AccessControlList xmlns="http://www.acme.com/Authorization/2013/01" >
            <AccessControlRecord Permissions="Fullcontrol" />
            <AccessControlRecord Permissions="DenyCreate DenyRead DenyUpdate DenyDelete" />
        </AccessControlList>' );
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
SELECT *
    ,Acr1Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[1]/@Permissions', 'NVARCHAR(128)')
    ,Acr2Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[2]/@Permissions', 'NVARCHAR(128)')
FROM dbo.Widget;

-- Setting a SINGLE value works fine
DECLARE @SingleValue NVARCHAR(100) = 'DenyCreate';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@SingleValue") cast as A:AccessPermissions ?')
FROM dbo.Widget;

-- Display values after
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
SELECT *
    ,Acr1Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[1]/@Permissions', 'NVARCHAR(128)')
    ,Acr2Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[2]/@Permissions', 'NVARCHAR(128)')
FROM dbo.Widget;

/* Setting MULTIPLE values *FAILS*
DECLARE @MultipleValues NVARCHAR(100) = 'DenyCreate DenyRead DenyUpdate DenyDelete';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@MultipleValues") cast as A:AccessPermissions ?')
FROM dbo.Widget;
*/

当我尝试使用“sql:column”设置多个值时,我遇到了同样的失败。

我发现这个资源(https://docs.microsoft.com/en-us/sql/xquery/type-casting-rules-in-xquery?view=sql-server-2017)说转换为列表类型或从列表类型转换为不允许; 我希望有一个解决方案或解决方法。

这可能使用 SQLXML 吗?如何?

提前致谢

4

1 回答 1

1

我必须承认,我以前从来没有处理过这个问题......

我必须承认,我没有找到一个简单的解决方案。如果你找到它,请告诉我。

即使使用文字也会导致相同的问题:字符串被转换为整个枚举它与允许的值之一不匹配,因此返回为空。

但你可以做

replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] 
with for $p in ("DenyCreate","DenyUpdate","DenyDelete") 
     return $p cast as A:AccessPermissions ?

这将使用 XQueryfor运行一个列表并逐个返回每个值,每个值分别转换为所需的类型。

但我发现将它与外部变量一起使用的唯一方法是动态 SQL。所以这有效,但相当难看:

DECLARE @MultipleValues VARCHAR(100)='DenyCreate DenyUpdate DenyDelete';

DECLARE @cmd NVARCHAR(MAX)=
'WITH XMLNAMESPACES ( ''http://www.acme.com/Authorization/2013/01'' AS A )
 UPDATE dbo.Widget
 SET ACL.modify(''replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with for $p in ("' + REPLACE(@MultipleValues,' ','","') + '") return $p cast as A:AccessPermissions ?'')
 FROM dbo.Widget;';

EXEC(@cmd);
于 2018-12-17T01:06:05.497 回答