Разделить документ XML на разделы - PullRequest
0 голосов
/ 17 января 2020

Я знаю, что об этом спрашивали много раз, но когда я ищу, я нахожу ответы за 12 лет go, и я ищу более актуальное решение. У меня есть файл XML, который выглядит как

<?xml version='1.0' encoding='UTF-8'?>
<PROJECTS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<APPLICATION_ID>9238972</APPLICATION_ID>
<ACTIVITY>R01</ACTIVITY>
<ADMINISTERING_IC>CA</ADMINISTERING_IC>
</row>
<row>
<APPLICATION_ID>9238973</APPLICATION_ID>
<ACTIVITY>R012</ACTIVITY>
<ADMINISTERING_IC>CA</ADMINISTERING_IC>
</row>
<row>
<APPLICATION_ID>9238974</APPLICATION_ID>
<ACTIVITY>R013</ACTIVITY>
<ADMINISTERING_IC>CA</ADMINISTERING_IC>
</row>
</PROJECTS>

И мне нужно сохранить каждый элемент <row> на сервере MS SQL 2016 как элемент XML.

Мой текущий код слишком медленный, чтобы сделать более 1 миллиона <row> элементов. У меня есть текстовые файлы с около 100 000 записей в каждом. Мой текущий код:

Dim rdr As New StreamReader(ofdXML.FileName)
While (rdr.Peek >= 0)
    varLine = rdr.ReadLine
    sTag = varLine.Contains("<row>")
    eTag = varLine.Contains("</row>")
    If sTag And eTag Then
        appLine = varLine
        If appLine.Contains("<row><APPLICATION_ID>") Then
            appID = appLine.Substring(Len("<row><APPLICATION_ID>"), appLine.IndexOf("/APPLICATION_ID") - Len("<row><APPLICATION_ID>") - 1)
        End If
    ElseIf sTag Then
        v1 = True
        appLine = varLine
        If appLine.Contains("<row><APPLICATION_ID>") Then
            appID = appLine.Substring(Len("<row><APPLICATION_ID>"),                               appLine.IndexOf("/APPLICATION_ID") - Len("<row><APPLICATION_ID>") - 1)
        End If
    ElseIf eTag Then
        appLine = appLine & varLine
        v1 = False
    ElseIf v1 Then
        appLine = appLine & varLine
        If appLine.Contains("<APPLICATION_ID>") Then
            Dim xi As Integer = appLine.IndexOf("_ID>") + 4
            appID = appLine.Substring(xi, appLine.IndexOf("/APPLICATION_ID") - (xi + 1))                         
        End If
    End If 

Я пробовал LINQ, но не могу получить правильный синтаксис для VB. NET, и это может быть быстрее. То, что я хотел бы, является более эффективным способом разделения и сохранения. В настоящее время создание файла с 100 000 элементов строки занимает 16 часов.

Ответы [ 4 ]

0 голосов
/ 17 января 2020

Вот метод, как разделить файл XML на фрагменты и INSERT каждый фрагмент XML в таблицу БД через BULK Load на стороне SQL Server. Вы можете упаковать эту SQL в хранимую процедуру и вызвать ее со стороны VB. NET.

SQL

DECLARE @tbl TABLE (ID INT IDENTITY PRIMARY KEY, APPLICATION_ID INT, xml_fragment XML);

;WITH XmlFile (xmlData) AS
(
    SELECT TRY_CAST(BulkColumn AS XML) 
    FROM OPENROWSET(BULK 'e:\Temp\Split XML file into fragments.xml', SINGLE_BLOB) AS x
)
, rs AS
(
    SELECT c.value('(APPLICATION_ID/text())[1]','INT') AS [APPLICATION_ID]
        , c.query('.') AS [xml_fragment]
    FROM XmlFile CROSS APPLY xmlData.nodes('(/PROJECTS/row)') AS t(c)
)
INSERT INTO @tbl (APPLICATION_ID, xml_fragment)
SELECT * FROM rs;

-- test
SELECT * FROM @tbl;

Выход

+----+----------------+---------------------------------------------------------------------------------------------------------------------+
| ID | APPLICATION_ID |                                                    xml_fragment                                                     |
+----+----------------+---------------------------------------------------------------------------------------------------------------------+
|  1 |        9238972 | <row><APPLICATION_ID>9238972</APPLICATION_ID><ACTIVITY>R01</ACTIVITY><ADMINISTERING_IC>CA</ADMINISTERING_IC></row>  |
|  2 |        9238973 | <row><APPLICATION_ID>9238973</APPLICATION_ID><ACTIVITY>R012</ACTIVITY><ADMINISTERING_IC>CA</ADMINISTERING_IC></row> |
|  3 |        9238974 | <row><APPLICATION_ID>9238974</APPLICATION_ID><ACTIVITY>R013</ACTIVITY><ADMINISTERING_IC>CA</ADMINISTERING_IC></row> |
+----+----------------+---------------------------------------------------------------------------------------------------------------------+
0 голосов
/ 17 января 2020

Использование XML Сериализация

Создание классов для представления вашего файла

Imports System.IO
Imports System.Xml.Serialization
<XmlRoot("PROJECTS")>
Public Class Projects
    <XmlElement("row")>
    Public Property Rows As List(Of Row)
End Class

Public Class Row
    <XmlElement("APPLICATION_ID")>
    Public Property Application_ID As Integer
    <XmlElement("ACTIVITY")>
    Public Property Activity As String
    <XmlElement("ADMINISTERING_IC")>
    Public Property Administering_IC As String
End Class

(я написал некоторый код для создания файла 1 миллиона <row> XML, который составляет ~ 140 МБ.)

Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim sw As New Stopwatch()
    sw.Start()
    Await Task.Factory.StartNew(AddressOf writeProjects)
    sw.Stop()
    Console.WriteLine($"Created file in {sw.ElapsedMilliseconds} ms.")
End Sub

Private Sub writeProjects()
    Dim p As New Projects()
    p.Rows = New List(Of Row)()
    For i = 1 To 1000000
        Dim r As New Row With {
            .Application_ID = i,
            .Activity = $"R0{i}",
            .Administering_IC = "CA"
        }
        p.Rows.Add(r)
    Next
    Using writer As New StreamWriter("filename.xml")
        Dim s As New XmlSerializer(GetType(Projects))
        s.Serialize(writer, p)
    End Using
End Sub

Создан файл за 2346 мс.

И используйте этот код для чтения файла и записи в базу данных

Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim sw As New Stopwatch()
    sw.Start()
    Dim p = Await Task.Factory.StartNew(AddressOf readProjects)
    sw.Stop()
    Console.WriteLine($"Read file in {sw.ElapsedMilliseconds} ms.")
    sw.Restart()
    Await Task.Factory.StartNew(AddressOf writeSQL, p, TaskCreationOptions.None)
    sw.Stop()
    Console.WriteLine($"Wrote to SQL in {sw.ElapsedMilliseconds} ms.")
End Sub

Private Function readProjects() As Projects
    Dim p As Projects
    Using reader As New StreamReader("filename.xml")
        Dim s As New XmlSerializer(GetType(Projects))
        p = DirectCast(s.Deserialize(reader), Projects)
    End Using
    Return p
End Function

Private Sub writeSQL(o As Object)
    Dim p = DirectCast(o, Projects)
    For Each r In p.Rows
        ' insert each row
        Dim q =
$"INSERT INTO [Table] (APPLICATION_ID, ACTIVITY, ADMINISTERING_IC) 
VALUES ({r.Application_ID}, '{r.Activity}', '{r.Administering_IC}')"
    Next
    ' or SQL bulk insert
End Sub

Считать файл за 3442 мс.
Записать в SQL за 625 мс.

В строке For Each r In p.Rows у вас есть реальные объекты в памяти со свойствами, такими как Application_ID, Activity и Administering_IC. Они вставляются легко индивидуально, что может занять некоторое время. Или вы можете сделать SQL массовую вставку. Получение объектов из файла в команды SQL INSERT занимает менее 5 секунд для 1 миллиона строк.

0 голосов
/ 17 января 2020

Вы можете использовать XElement :

Dim xml = XElement.Load("PATH_TO_FILE")
Dim rows = xml.<row>
For Each row In rows
    Dim app_id = row.<APPLICATION_ID>.First.Value
    Dim activity = row.<ACTIVITY>.First.Value
    Dim adm_ic = row.<ADMINISTERING_IC>.First.Value
    WriteLine($"app_id: {app_id} activity {activity}, adm_ic: {adm_ic}")
Next
0 голосов
/ 17 января 2020

Если это однократный импорт, вы можете написать конвертер, чтобы прочитать файл XML и выпустить файл с разделителями. Затем вы можете легко импортировать его, используя команду SQL Server BULK INSERT или мастер импорта данных.

Код для преобразования будет выглядеть примерно так ...

    Using sw As New System.IO.StreamWriter("outputfile.txt")
        Dim xdoc As New System.Xml.XmlDocument
        xdoc.Load("InpurtFilename")
        For Each row As System.Xml.XmlNode In xdoc.SelectNodes("//xsi:row")
            Dim ApplicationId As String = row.SelectSingleNode("xsi:APPLICATION_ID").InnerText
            Dim Activity As String = row.SelectSingleNode("xsi:ACTIVITY").InnerText
            Dim AdministeringIC As String = row.SelectSingleNode("xsi:ADMINISTERING_IC").InnerText
            sw.WriteLine(String.Format("{0}|{1}|{2}", ApplicationId, Activity, AdministeringIC))
        Next
    End Using
...