javascript - 将XML中的Web数据传递到SQL Server数据库的明智方法

标签 javascript sql-server xml asp-classic

在探索了将Web数据传递到数据库以进行更新的几种不同方式之后,我想知道XML是否可能是一个很好的策略。该数据库当前为SQL2000。几个月后它将移至SQL 2005,如果需要,我将能够进行更改,但是现在我需要一个SQL 2000解决方案。

首先,有问题的数据库使用EAV model。我知道这种数据库通常不受欢迎,因此出于这个问题的目的,请接受这不会改变。

当前的更新方法使Web服务器将值(已全部转换为正确的基础类型,然后转换为sql_variant)插入到临时表中。然后运行一个存储过程,该存储过程期望临时表存在,并负责根据需要更新,插入或删除内容。

到目前为止,一次只需要更新一个元素。但是现在,要求能够一次编辑多个元素,并且还必须支持层次结构元素,每个层次结构元素可以具有自己的属性列表。这是我手工键入的一些示例XML,以演示我的想法。

请注意,在此数据库中,实体为元素,ID为0表示“创建”,也就是插入新项。

<Elements>
  <Element ID="1234">
    <Attr ID="221">Value</Attr>
    <Attr ID="225">287</Attr>
    <Attr ID="234">
      <Element ID="99825">
        <Attr ID="7">Value1</Attr>
        <Attr ID="8">Value2</Attr>
        <Attr ID="9" Action="delete" />
      </Element>
      <Element ID="99826" Action="delete" />
      <Element ID="0" Type="24">
        <Attr ID="7">Value4</Attr>
        <Attr ID="8">Value5</Attr>
        <Attr ID="9">Value6</Attr>
      </Element>
      <Element ID="0" Type="24">
        <Attr ID="7">Value7</Attr>
        <Attr ID="8">Value8</Attr>
        <Attr ID="9">Value9</Attr>
      </Element>
    </Attr>
    <Rel ID="3827" Action="delete" />
    <Rel ID="2284" Role="parent">
      <Element ID="3827" />
      <Element ID="3829" />
      <Attr ID="665">1</Attr>
    </Rel>
    <Rel ID="0" Type="23" Role="child">
      <Element ID="3830" />
      <Attr ID="67"
    </Rel>
  </Element>
  <Element ID="0" Type="87">
    <Attr ID="221">Value</Attr>
    <Attr ID="225">569</Attr>
    <Attr ID="234">
      <Element ID="0" Type="24">
        <Attr ID="7">Value10</Attr>
        <Attr ID="8">Value11</Attr>
        <Attr ID="9">Value12</Attr>
      </Element>
    </Attr>
  </Element>
  <Element ID="1235" Action="delete" />
</Elements>


一些属性是直接值类型,例如AttrID221。但是AttrID 234是一种特殊的“多值”类型,可以在其下具有一系列元素,每个元素可以具有一个或多个值。仅当创建新项目时才需要显示类型,因为ElementID完全隐含了该类型(如果已经存在)。我可能只支持传递更改的项目(由javascript检测)。而且,在Attr元素上也可能有一个Action =“ Delete”,因为NULL被视为“未选择”-有时,非常重要的一点是要知道是否有意回答是/否问题否或是否有人打扰说是的

还有另一种数据,一种关系。目前,在UI中对这些内容进行编辑时,它们通过单独的AJAX调用进行了更新,但我希望包含这些内容,以便可以取消对关系的更改(现在,一旦更改,就可以完成)。因此,它们也确实是元素,但是它们称为Rel而不是Element。关系被实现为ElementID1和ElementID2,因此上述XML中的RelID 2284在数据库中的位置为:

ElementID 2284 ElementID1 1234 ElementID2 3827

目前不支持在一个关系中有多个孩子,但是以后会更好。

这种策略和示例XML是否有意义?有没有更明智的方法?我只是在寻找一些广泛的批评意见,以帮助我摆脱困境。您想评论的任何方面都会有所帮助。

Web语言恰好是Classic ASP,但是在某些时候可能会更改为ASP.Net。像Linq或nHibernate这样的持久性引擎现在可能是不可接受的-我只是想在没有大量开发时间的情况下增强已经运行的应用程序。

我将选择能够显示经验的答案,并在不做哪些事情,对我打算做的事情的确认以及关于其他事情的建议的良好警告之间保持平衡。我将使其尽可能客观。

附言我想处理unicode字符以及非常长的字符串(10k +)。

更新

我已经进行了一段时间,并且使用ADO Recordset Save-To-Stream技巧使创建XML变得非常容易。结果似乎相当快,但是如果速度成为问题,我可能会重新考虑。

同时,我的代码可一次处理页面上任意数量的元素和属性,包括一次性更新,删除和创建新项目。

对于所有元素,我都选择了类似的方案:


现有数据元素

示例:输入名称e12345_a678(元素12345,属性678),输入值是该属性的值。
新元素

Javascript将类型所需的一组HTML元素的隐藏模板复制到页面上的正确位置,增加一个计数器以获得该项目的新ID,并将该数字添加到表单项目的名称之前。

var newid = 0;

function metadataAdd(reference, nameid, value) {
   var t = document.createElement('input');
   t.setAttribute('name', nameid);
   t.setAttribute('id', nameid);
   t.setAttribute('type', 'hidden');
   t.setAttribute('value', value);
   reference.appendChild(t);
}

function multiAdd(target, parentelementid, attrid, elementtypeid) {
   var proto = document.getElementById('a' + attrid + '_proto');
   var instance = document.createElement('p');
   target.parentNode.parentNode.insertBefore(instance, target.parentNode);
   var thisid = ++newid;
   instance.innerHTML = proto.innerHTML.replace(/{prefix}/g, 'n' + thisid + '_');
   instance.id = 'n' + thisid;  
   instance.className += ' new';
   metadataAdd(instance, 'n' + thisid + '_p', parentelementid);
   metadataAdd(instance, 'n' + thisid + '_c', attrid);
   metadataAdd(instance, 'n' + thisid + '_t', elementtypeid);
   return false;
}


示例:模板输入名称_a678变为n1_a678(一个新元素,页面上的第一个元素,属性678)。这个新元素的所有属性都标记有相同的前缀n1。下一个新项将是n2,依此类推。创建了一些隐藏的表单输入:

n1_t,值是要创建的元素的元素类型
n1_p,值是元素的父代ID(如果是关系)
n1_c,值是元素的子ID(如果是关系)
删除元素

将以e12345_t形式创建一个隐藏输入,并将其值设置为0。禁用了显示该属性值的现有控件,因此它们不包含在表单发布中。因此,“将类型设置为0”被视为删除。


使用此方案,页面上的每个项目都具有唯一的名称,可以正确区分,并且可以正确表示每个动作。

发布表单后,以下是构建使用的两个记录集之一的示例(经典ASP代码):

Set Data = Server.CreateObject("ADODB.Recordset")
Data.Fields.Append "ElementID", adInteger, 4, adFldKeyColumn
Data.Fields.Append "AttrID", adInteger, 4, adFldKeyColumn
Data.Fields.Append "Value", adLongVarWChar, 2147483647, adFldIsNullable Or adFldMayBeNull
Data.CursorLocation = adUseClient
Data.CursorType = adOpenDynamic
Data.Open


这是值的记录集,另一个是元素本身的记录集。

我逐步浏览发布的表单,并为元素记录集使用Scripting.Dictionary,其中填充了具有所需属性的自定义Class实例,因此我可以零碎添加值,因为它们并不总是按顺序排列。新元素被添加为负数,以将其与常规元素区分开(而不是需要单独的列来指示它是新元素还是寻址现有元素)。我使用正则表达式将形式键拆开:“ ^(e | n)([0-9] {1,10})_(a | p | t | c)([0-9] {0,10 })$“

然后,添加属性如下所示。

Data.AddNew
ElementID.Value = DataID
AttrID.Value = Integerize(Matches(0).SubMatches(3))
AttrValue.Value = Request.Form(Key)
Data.Update


ElementID,AttrID和AttrValue是对记录集字段的引用。该方法比每次使用Data.Fields(“ ElementID”)。Value都快得多。

我遍历了元素更新字典,并忽略了没有所有正确信息的任何信息,将好的信息添加到了记录集中。

然后,我这样调用数据更新存储过程:

Set Cmd = Server.CreateObject("ADODB.Command")
With Cmd
   Set .ActiveConnection = MyDBConn
   .CommandType = adCmdStoredProc
   .CommandText = "DataPost"
   .Prepared = False
   .Parameters.Append .CreateParameter("@ElementMetadata", adLongVarWChar, adParamInput, 2147483647, XMLFromRecordset(Element))
   .Parameters.Append .CreateParameter("@ElementData", adLongVarWChar, adParamInput, 2147483647, XMLFromRecordset(Data))
End With
Result.Open Cmd ' previously created recordset object with options set


这是执行xml转换的函数:

Private Function XMLFromRecordset(Recordset)
   Dim Stream
   Set Stream = Server.CreateObject("ADODB.Stream")
   Stream.Open
   Recordset.Save Stream, adPersistXML
   Stream.Position = 0
   XMLFromRecordset = Stream.ReadText
End Function


万一网页需要知道,SP会返回任何新元素的记录集,显示它们的页面值和它们的创建值(因此,我可以看到n1现在是e12346)。

这是存储过程中的一些关键片段。请注意,虽然现在我可以很快切换到2005,但现在是SQL 2000:

CREATE PROCEDURE [dbo].[DataPost]
   @ElementMetaData ntext,
   @ElementData ntext
AS
DECLARE @hdoc int

--- snip ---

EXEC sp_xml_preparedocument @hdoc OUTPUT, @ElementMetaData, '<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema" />'
INSERT #ElementMetadata (ElementID, ElementTypeID, ElementID1, ElementID2)
SELECT *
FROM
   OPENXML(@hdoc, '/xml/rs:data/rs:insert/z:row', 0)
   WITH (
      ElementID int,
      ElementTypeID int,
      ElementID1 int,
      ElementID2 int
   )
ORDER BY ElementID -- orders negative items (new elements) first so they begin counting at 1 for later ID calculation

EXEC sp_xml_removedocument @hdoc

--- snip ---

UPDATE E
SET E.ElementTypeID = M.ElementTypeID
FROM
   Element E
   INNER JOIN #ElementMetadata M ON E.ElementID = M.ElementID
WHERE
   E.ElementID >= 1
   AND M.ElementTypeID >= 1


以下查询将负的新元素ID与新插入的ID进行关联:

UPDATE #ElementMetadata -- Correlate the new ElementIDs with the input rows
SET NewElementID = Scope_Identity() - @@RowCount + DataID
WHERE ElementID < 0


其他基于集合的查询会执行所有其他工作,以验证是否允许属性,数据类型正确以及插入,更新和删除元素和属性。

我希望这一短暂的失败有一天对其他人有用!将ADO Recordsets转换为XML流对我来说是一个巨大的胜利,因为它节省了各种时间,并且已经定义了一个名称空间和架构,可以使结果正确显示。

使用具有2个输入的扁平XML格式比坚持将所有内容都包含在单个XML流中的理想状态要容易得多。

最佳答案

如果我理解正确,您会对使用XML作为数据库和应用程序(在本例中为Web应用程序)之间的数据格式的利弊感兴趣。

如果您碰巧将所有数据作为方便的数据包插入/更新/删除在客户端中,那么实际上以XML形式发送数据是有意义的。简单的原因是,这将允许单个数据库往返于服务器,因此减少往返次数始终是一个好主意。但是,最重要的优点是您可以利用数据库性能的圣杯:面向集合的处理。通过使用XML方法(特别是nodesvalue),并结合了一些XPath-fu技巧,您可以将从应用程序接收到的整个XML参数分解为关系集,并使用面向集的操作来进行数据库写操作。

以您的帖子中的XML为例,可以说它作为XML类型的名为@x的参数传递。您可以将其切成属性,以合并到现有元素中:

select x.value(N'@ID', N'int') as ID,
  x.value(N'.', N'varchar(max)') as [Value]
from  @x.nodes('//Element[not(@Action="delete") and not (@ID=0)]/Attr') t(x)


您可以切入新元素的属性:

select x.value(N'@ID', N'int') as ID,
  x.value(N'.', N'varchar(max)') as [Value]
from  @x.nodes('//Element[@ID=0]/Attr') t(x);


您可以切碎要删除的元素:

select x.value(N'@ID', N'int') as ID
from  @x.nodes('//Element[@Action="delete"]') t(x);


这些集合可以通过普通的SQL DML进行操作:一次通过插入,删除,更新或merged到EAV表中。请注意,我在这里展示的XML分解是琐碎的,对您来说可能是不正确的,但只是为了展示这样做的方法。

现在不知道这是否是最佳途径。有太多的变量和变动的部分,它们主要取决于您的开发团队技能和现有代码库。可以肯定的是,XML是调用数据库以更新集合的一种很好的格式,但是XML也有缺点:冗长而繁琐,比二进制格式解析起来慢,并且实际上程序员很难完全理解:一旦您越过'<'和'>'的糖衣,在XPath,XQuery,名称空间,编码,cdata和其余部分中都有一个深层(有时是混乱的)层。

我想说吧,原型,让我们知道进展情况...

关于javascript - 将XML中的Web数据传递到SQL Server数据库的明智方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2394644/

相关文章:

javascript - React Nextjs 路由传递 Redux Store

sql-server - 如何安排在SQL Server上运行SQL脚本?

javascript - 从表单中添加和删除元素,第一次单击有效,后续单击无效

sql - 如何动态选择在存储过程中选择哪个表(具有相同架构)

python - MS SQL + Python (IronPython) 超时

iphone - 阿拉伯语内容以奇怪的字符显示

xml - 我如何使用 XSLT 重复一个 Action X 次

xml - 单个 RSS xml 中的多个 channel - 它是否合适?

javascript - 下拉菜单 - 从 CSS 移动到使用 JavaScript 运行

javascript - div 组的通用 js