将 XML 解析为 sql 参数、datetime2 和位问题

Posted

技术标签:

【中文标题】将 XML 解析为 sql 参数、datetime2 和位问题【英文标题】:Parse XML to sql parameters, datetime2 and bit questions 【发布时间】:2014-02-27 11:05:15 【问题描述】:

我有一个无法解决的难题和一个一般性问题:我正在解析来自供应商的 XML 文件,但无法修改 API 输出。感谢这里所有出色的人,我设法编写代码以将数据转储到 SQL Server 2008 R2 中。我不知道如何完成的一件事是将<date> 节点从其当前形式转换为datetime2(或您可以推荐的任何datetime 格式)。我尝试了许多不同的方法,每一种都比前一次失败得更惨。

我得到的错误是

将数据类型 nvarchar 转换为 datetime2 时出错

另外,我认为必须有一种更简单的方法来循环遍历子节点。还有 15 个文本字段,如果我可以让 SQL 服务器以某种方式完成繁重的工作,我是否应该能够遍历每个子注释并将名称/值对作为字符串参数发送?

提前致谢。

XML:

<results>
    <result>
        <title>Application Developer</title>
    ...
    <date>Thu, 30 Jan 2014 14:09:00 GMT</date>
    <expired>false</expired>
</result>

sp:

INSERT INTO dbo.StaffWriteImport
          ( 
        title ,
        ...
        jbdate ,
        expired 
       ) 
     VALUES 
      ( 
     @title ,
     ...
     @jbdate ,
     CAST(CASE @expired WHEN 1 THEN 1 ELSE 0 END AS BIT)   
          ) 

C#:

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        
            using (Stream dataStream = response.GetResponseStream())
            
                using (StreamReader reader = new StreamReader(dataStream))
                
                    string responseFromServer = reader.ReadToEnd();
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(responseFromServer);
                    XmlNodeList Xn = xmlDoc.SelectNodes("//response/results/result");

                    using (SqlConnection conn = new SqlConnection("data source=myconnectionstring yada yada yada;"))

using (SqlCommand cmd = new SqlCommand(sqlStmt, conn))

    foreach (XmlNode xnode in Xn)
    
        XmlElement resultElement = (XmlElement) xnode;
        conn.Open();
        cmd.CommandType = CommandType.StoredProcedure;

        if(!string.IsNullOrEmpty(resultElement.GetElementsByTagName("title")[0].InnerText))
         cmd.Parameters.AddWithValue("title", resultElement.GetElementsByTagName("title")[0].InnerText);   
        ...

        if (!string.IsNullOrEmpty(resultElement.GetElementsByTagName("date")[0].InnerText))
           var dateformat = "yyyy-MM-dd HH:mm:ss:fff";
            cmd.Parameters.AddWithValue("jbdate", resultElement.GetElementsByTagName("date")[0].InnerText); 

        if (!string.IsNullOrEmpty(resultElement.GetElementsByTagName("expired")[0].InnerText))
         
            int exprd = 0;
            if (resultElement.GetElementsByTagName("expired")[0].InnerText == "true")  exprd = 1; 
            cmd.Parameters.AddWithValue("expired", exprd) ; 

        cmd.ExecuteNonQuery();
        cmd.Parameters.Clear();
        conn.Close();

    


【问题讨论】:

你没有对你的 dateFormat 变量做任何事情,你只是创建它。这可能是一个问题。还使用 XDocument 将使代码更漂亮/更易于使用。最后,如果要插入大量这些结果,请考虑使用 TVP(表值参数)。您将创建一个包含所有结果的 DataTable 并将整个内容传递给 SP。 这是我混淆示例中的一个错字。实际上,我已经尝试过无数种不同的方式。 谢谢大卫。您能否指出一个与我正在做的类似的 XDocument 示例?我自己找不到任何例子。谢谢 你运行的是什么版本的sql server? 问题中说的是 SQL 2008 R2... 【参考方案1】:

我会这样做:

    我会让我的存储过程使用表值参数来一次获取所有结果。此功能自 SQL 2008 起提供,因此您可以使用它。 我将使用 XLinq 进行解析,然后创建结果的 DataTable(需要将其传递给采用表值参数的存储过程)。

SQL

我这里只使用了 3 列作为示例,您可以添加所有需要的列。

-- This is needed to use as a parameter in an SP
CREATE TYPE XmlResultType AS TABLE 
(
    Title varchar(50),
    [Date] datetime2(7),
    Expired bit
)
GO

-- Simple stored procedure just copies results from TVP into actual table
Create Procedure usp_InsertXmlResultsByTable
(
    @XmlResultTable dbo.XmlResultType READONLY
) 
As
    Insert Into XmlResult
    Select Title, [Date], Expired
    From @XmlResultTable

还有 C# 示例:

注意,我知道您正在调用 Web 服务,所以我的示例中,假设 xml 变量是您的 Web 服务调用的结果。无论哪种情况,您都可以使用 XDocument.Parse(xml) 来创建 XDocument 对象。

        string xml = @"
            <results>
                <result>
                    <title>Application Developer</title>
                    <date>Thu, 30 Jan 2014 14:09:00 GMT</date>
                    <expired>false</expired>
                </result>
                <result>
                    <title>Incomplete</title>
                    <date></date>
                    <expired></expired>
                </result>
                <result>
                    <title>Professional Time-waster</title>
                    <date>Fri, 31 Jan 2014 18:35:00 GMT</date>
                    <expired>false</expired>
                </result>
            </results>";

        XDocument xDoc = XDocument.Parse(xml);

        // Create a DataTable that mimics the Table Valued Parameter structure.
        DataTable dt = new DataTable();
        dt.Columns.Add("Title", typeof(string));
        dt.Columns.Add("Date", typeof(DateTime));
        dt.Columns.Add("Expired", typeof(bool));


        // Just make the code cleaner
        Func<string, string> getString = s => string.IsNullOrEmpty(s) ? null : s;
        Func<string, DateTime?> getDateTime = d => string.IsNullOrEmpty(d) ? null : (DateTime?)DateTime.Parse(d);
        Func<string, bool?> getBool = b => string.IsNullOrEmpty(b) ? null : (bool?)Convert.ToBoolean(b);

        // here we populate the table variable, using above Funcs<> to handle nulls
        // This code does assume that your date format, when present, will always be parseable.
        xDoc.Descendants("result")
            .ToList()
            .ForEach(r =>
                dt.Rows.Add(
                    getString(r.Element("title").Value),
                    getDateTime(r.Element("date").Value),
                    getBool(r.Element("expired").Value)
                )
            );

        // Now all that's left is to call the stored procedure and pass the DataTable to it
        string connString = "your connection string";
        using(var conn = new SqlConnection(connString))
        using(var cmd = new SqlCommand())
        
            conn.Open();
            cmd.Connection = conn;
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = "usp_InsertXmlResultsByTable";
            SqlParameter tableParameter = cmd.Parameters.AddWithValue("XmlResultTable", dt);
            tableParameter.SqlDbType = SqlDbType.Structured;
            cmd.ExecuteNonQuery();
            conn.Close();
        

这是一种稍微不同的方法,但它的性能会更好,因为您只需从 Web 服务中调用一次数据库即可获取整个 XML 有效负载。我希望这会有所帮助!

【讨论】:

非常感谢,就像一个魅力!我需要一周的时间才能弄清楚其中的一半,但我很欣赏它利用 .Net 和 SQL 服务器更新的方式!【参考方案2】:

因此您无需在应用程序端对其进行粉碎。如果您可以将日期修复为 sql 可以读取的内容,您可以将 xml 传递给 sql,让 sql server 将其切碎并将其插入您需要的表中。这样做的好处是只调用存储过程一次

  DECLARE @testXML AS XML = '<Root>
    <node>
    <id>1</id>
    <name>test</name>
    <result>cool</result>
    </node>
    <node>
    <id>2</id>
    <name>sweet</name>
    <result>uncool</result>
    </node>
    </Root>'

      SELECT i.value('(id)[1]','INT') AS id, 
           i.value('(name)[1]','VARCHAR(50)') AS name,
           i.value('(result)[1]','varchar(50)') AS result
    FROM @testXML.nodes('Root/node')x(i)

【讨论】:

以上是关于将 XML 解析为 sql 参数、datetime2 和位问题的主要内容,如果未能解决你的问题,请参考以下文章

将 DATETIME 从 SQLite 解析为 java.SQL.Timestamp 时出错

从 SQL 数据库导入表并按日期过滤行时,将 Pandas 列解析为 Datetime

从 SQL 数据库导入表并按日期过滤行时,将 Pandas 列解析为 Datetime

如何将 DateTime 的 SQL Server XML 类型值 (xsi:nil) 转换为 null

错误 SQL Server 2012:未为函数 X 提供参数

C# 将 DateTime 转换为 Sql Server 2005 格式