Сформулируйте правильно сформированный xml из фрагментов Xml, используя С#

Я формулирую xmlnodes из цикла. так что он делает

   var settings = new XmlWriterSettings();
   settings.OmitXmlDeclaration = true;
   settings.Indent = true;
   var ns = new XmlSerializerNamespaces();
   ns.Add("", "");
   foreach (Person human in bar)
    {
        var serializer = new XmlSerializer(typeof(Person));
        using (var stream = new FileStream(filepath, FileMode.Append))
        using (var writer = XmlWriter.Create(stream, settings))
        {
            serializer.Serialize(writer, human, ns);
        }
    }

Он формулирует фрагменты xml, когда цикл завершен, выходной XML выглядит следующим образом

    <Person>
  <Name>mar8a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar1a</Name>
  <Age>1</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar2a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar3a</Name>
  <Age>1</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person><Person>
  <Name>mar4a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar5a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar6a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar7a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar8a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>

проблема, с которой я столкнулся, заключается в том, как изменить его и сделать его хорошо сформированным xml с корневым узлом и объявлением в конце цикла.

Я попробовал следующую концепцию, но мне не повезло, потому что она ограничивает меня тем, что я не могу записывать корневые элементы на писатель.

        StringBuilder output = new StringBuilder();
        XmlReaderSettings ws = new XmlReaderSettings();
        ws.ConformanceLevel = ConformanceLevel.Fragment;
        String xmlString =
                @"<Item>test with a child element stuff</Item>
                <Item>test with a child element stuff</Item>";
        // Create an XmlReader
        using (XmlReader reader = XmlReader.Create(new StringReader(xmlString), ws))
        {
            XmlWriterSettings ws2 = new XmlWriterSettings();
            ws2.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(output, ws2))
            {
                writer.WriteStartDocument();
                // Parse the file and display each of the nodes.
                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            writer.WriteStartElement(reader.Name);
                            break;
                        case XmlNodeType.Text:
                            writer.WriteString(reader.Value);
                            break;
                        case XmlNodeType.XmlDeclaration:
                        case XmlNodeType.ProcessingInstruction:
                            writer.WriteProcessingInstruction(reader.Name, reader.Value);
                            break;
                        case XmlNodeType.Comment:
                            writer.WriteComment(reader.Value);
                            break;
                        case XmlNodeType.EndElement:
                            writer.WriteFullEndElement();
                            break;
                    }
                }
                writer.WriteEndDocument();

            }
        }

Обновить!!

вот код, который сериализует список моей реализации сериализатора

        public static async Task WriteXMLAsync<T>(this List<T> listRows, T entity, VMEXPORT[] arrVmExport, string filePath)
        where T : class
    {
        XmlWriterSettings Xmlsettings = new XmlWriterSettings();
        Xmlsettings.Indent = true;
        Xmlsettings.OmitXmlDeclaration = false;
        Xmlsettings.NewLineOnAttributes = true;
        Xmlsettings.Async = true;
        Xmlsettings.Encoding = Encoding.UTF8;
        Xmlsettings.CheckCharacters = false;

        XmlAttributeOverrides Xmloverrides = new XmlAttributeOverrides();
        XmlAttributes Xmlattribs = new XmlAttributes();
        Xmlattribs.XmlIgnore = true;
        Xmlattribs.XmlElements.Add(new XmlElementAttribute("SfiObjectState"));
        Xmloverrides.Add(typeof(T), "SfiObjectState", Xmlattribs);


        if (!File.Exists(filePath))
        {
            using (var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 4096, true))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<T>), Xmloverrides);
                using (XmlWriter xmlWriter = XmlWriter.Create(fileStream, Xmlsettings))
                {
                    serializer.Serialize(xmlWriter, listRows);
                    await xmlWriter.FlushAsync();
                }
            }
        }
        else
        {

            using (var fileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None, 4096, true))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<T>), Xmloverrides);
                using (XmlWriter xmlWriter = XmlWriter.Create(fileStream, Xmlsettings))
                {
                    serializer.Serialize(xmlWriter, listRows);
                    await xmlWriter.FlushAsync();
                }
            }


        }

    }

А вот что повторяет описанный выше метод, реализующий взятие и пропуск

 public async Task WriteXmlDataAsync<TEntity>(IQueryable<TEntity> listToWrite, [DataSourceRequest]DataSourceRequest dataRequest,
                                          int countno, VMEXPORT[] vmExportarr, CancellationToken token,
                                          TEntity entity, string csvFileNametx, string XmlFilePathtx)
      where TEntity : class
    {
        dataRequest.GroupingToSorting();
        int datapageno = (countno / GeneralConst.L_MAX_EXPORT_REC) + 1;
        for (int ctrno = 1; ctrno <= datapageno; )
        {
            if (token.IsCancellationRequested)
            {
                RemoveTask(csvFileNametx);
                token.ThrowIfCancellationRequested();
            }
            dataRequest.Page = ctrno;
            dataRequest.PageSize = GeneralConst.L_MAX_EXPORT_REC;
            var dataSourceResult = listToWrite.ToDataSourceResult(dataRequest);
            await dataSourceResult.Data.Cast<TEntity>().ToList().WriteXMLAsync(entity, vmExportarr, XmlFilePathtx);
            ctrno = ctrno + 1;
            int percentageno = (ctrno * 100) / datapageno;
            if (percentageno > 100) percentageno = 100;
            UpdateTask(csvFileNametx, percentageno);
        }

    }

person Edu Cielo    schedule 20.05.2015    source источник
comment
Разве вы не можете просто написать весь список в XML (может потребоваться добавить класс-оболочку, чтобы иметь корневой элемент, который вам нравится)?   -  person Alexei Levenkov    schedule 20.05.2015
comment
мне нужно добавить фрагменты xml в пакетном режиме. (проблемы масштабируемости) создание фрагментов асинхронным способом для добавления пары сотен записей по очереди. если я напишу весь список в своем сценарии, он будет иметь несколько корневых элементов   -  person Edu Cielo    schedule 20.05.2015
comment
XML, вероятно, не лучший формат для данных, которые вы пытаетесь сохранить, чем ... В любом случае ваш образец не записывает корневой узел любого типа (WriteStartDocument не создает для вас волшебным образом корневой узел).   -  person Alexei Levenkov    schedule 20.05.2015
comment
спасибо за это разъяснение .. Мне удалось сделать это в файле csv. Я не могу создать весь List‹bar› в xml с помощью класса XmlSerializer, но проблема в том, что когда я добавляю другой, это еще один правильно сформированный xml, так что происходит то, что будет много корневых элементов. Любое другое предложение @AlexeiLevenkov   -  person Edu Cielo    schedule 20.05.2015
comment
Если вы не можете загрузить все элементы в списке людей одновременно, вы можете использовать XmlSerializer для потоковой передачи всего списка фрагментами, загружаемыми по запросу. См. здесь пример того, как это сделать: of-objects-to-avoid" title="используйте c Sharp xmlserializer для записи в куски для больших наборов объектов, которых следует избегать"> stackoverflow.com/questions/28837438/   -  person dbc    schedule 20.05.2015
comment
Я даже не могу понять, как бы вы это реализовали. значит ли это, что вы загружаете сериализатор кусками??   -  person Edu Cielo    schedule 20.05.2015
comment
Да, точно. Вы написали, что мне нужно добавить фрагменты xml в пакетном режиме. Итак, у вас есть какой-то метод для создания списков вашего класса Person по частям, который вы вызываете несколько раз, верно? На что это похоже?   -  person dbc    schedule 20.05.2015
comment
я поставлю весь код. я обновлю   -  person Edu Cielo    schedule 20.05.2015
comment
@dbc, это моя реализация сериализатора. я вызываю его в цикле, чтобы добавить файл, но когда он зацикливается, он добавляет несколько корней из-за сериализатора, я думаю, что это выполнимо, если бы я прошел через вашу реализацию, но как мой цикл будет использовать только один сериализатор, подаваемый из цикла ??   -  person Edu Cielo    schedule 20.05.2015
comment
Я вижу, вы работаете со многими async/await юнитами. Это не соответствует моему предыдущему решению, которое загружает содержимое списка по запросу из XmlSerializer. Можно ли сделать всю операцию одной большой задачей?   -  person dbc    schedule 20.05.2015
comment
Ранее вы упомянули проблемы масштабируемости. Означает ли это, что после записи списка он слишком велик для загрузки обратно в память?   -  person dbc    schedule 20.05.2015
comment
да как-то так. в моем случае сейчас это 5 миллионов записей и 1,68 ГБ XML-файлов с отступом и 1,2, если нет ... кстати, это веб-приложение. одна большая задача может быть. мы можем обернуть его в асинхронный или фоновый рабочий процесс   -  person Edu Cielo    schedule 20.05.2015


Ответы (3)


Вариант 1

Вы можете сделать что-то быстрое и грязное, как это после того, как файл XML будет написан:

    public static void AddOuterElement(string fileName, string elementName)
    {
        var startElement = string.Format(@"<{0}>", elementName);
        var endElement = string.Format(@"</{0}>", elementName);

        var tmpName = Path.GetTempFileName();
        try
        {
            using (var writer = new StreamWriter(tmpName, false, Encoding.UTF8))
            {
                writer.WriteLine(startElement);
                foreach (var line in File.ReadLines(fileName))  // Reads lines incrementally rather than all at once.
                    writer.WriteLine(line);
                writer.WriteLine(endElement);
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
            try
            {
                System.IO.File.Delete(tmpName);
            }
            catch (Exception)
            {
            }
            throw;
        }
        System.IO.File.Delete(fileName);
        System.IO.File.Move(tmpName, fileName);
    }

Это требует двукратной записи XML-файла.

Вариант 2

Предположим, у вас есть метод, который может возвращать списки вашего класса Person по частям, скажем, со следующей подписью:

    IEnumerable<IEnumerable<Person>> GetPeopleInChunks()
    {
        // Query the database in chunks of 200 and yield return each list.
    }

Затем вы можете использовать следующие классы, адаптированные из этого ответа, чтобы последовательно сериализовать всех людей в базе данных, даже не загружая их всех в память сразу:

// Proxy class for any enumerable with the requisite `Add` methods.
public class EnumerableProxy<T> : IEnumerable<T>
{
    [XmlIgnore]
    public IEnumerable<T> BaseEnumerable { get; set; }

    public void Add(T obj)
    {
        throw new NotImplementedException();
    }

    #region IEnumerable<T> Members

    public IEnumerator<T> GetEnumerator()
    {
        if (BaseEnumerable == null)
            return Enumerable.Empty<T>().GetEnumerator();
        return BaseEnumerable.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

[XmlRoot("People")]
public class People
{
    [XmlIgnore]
    public IEnumerable<Person> Results { get; set; }

    [XmlElement("Person")]
    public EnumerableProxy<Person> ResultsProxy
    {
        get
        {
            return new EnumerableProxy<Person> { BaseEnumerable = Results };
        }
        set
        {
            throw new NotImplementedException();
        }
    }
}

А потом:

    public void WriteXml(string fileName)
    {
        var people = new People { Results = GetPeopleInChunks().SelectMany(chunk => chunk) };
        using (var writer = XmlWriter.Create(fileName))
        {
            new XmlSerializer(typeof(People)).Serialize(writer, people);
        }
    }
person dbc    schedule 20.05.2015
comment
это выглядит хорошо. доходность в конечном итоге должна работать. что я имел в виду, я должен сделать это так, как это количество данных, чтобы избежать исключения oom .. это подает сериализатор на выход getchunks, верно? - person Edu Cielo; 20.05.2015
comment
@EduCielo - это правильно передает сериализатору выход getchunks - точно. И SelectMany делает его похожим на одно перечисляемое, а не на кучу кусков. - person dbc; 20.05.2015
comment
объяснил здесь более чистый способ, чем в вашем ответе на другой пост. Это делает Xmlserializer более гладким.. - person Edu Cielo; 20.05.2015

Попробуйте что-то вроде ниже. Вам также необходимо иметь только один уровень тегов на корневом уровне.

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlStr);


            XmlDeclaration xDeclare = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
            XmlNode docNode = doc.CreateXmlDeclaration("1.0", "UTF-8", null);

            doc.InsertBefore(xDeclare, doc.FirstChild);

            //or
            string xml1 ="<Root>" + "Your XML" + "</Root>";

            //or
            string xml2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root>" + "Your XML" + "</Root>"; 
​
person jdweng    schedule 20.05.2015
comment
да, в этом подходе я написал файл в виде огромного XML-файла, затем загрузил его как строку, а затем добавил, чтобы закрыть теги. думаю лучше не будет - person Edu Cielo; 20.05.2015
comment
Напишите объявление и корневой тег в начале файла. Затем запишите огромные данные в файл. Наконец, напишите закрывающий тег перед закрытием файла. Таким образом, вам не придется открывать, читать и записывать огромные файлы во второй раз. - person jdweng; 20.05.2015

Предполагая, что все ваши узлы Person находятся в строке с некоторым описанием, простой способ превратить это в документ будет выглядеть примерно так:

    XmlDocument oXmlDocument = new XmlDocument();
    oXmlDocument.AppendChild(oXmlDocument.CreateXmlDeclaration("1.0", "UTF-8", ""));

    string sFileContents = "XML Fragments In Here";

    XmlNode oRootXmlNode = oXmlDocument.CreateElement("Root");
    oRootXmlNode.InnerXml = sFileContents;

    oXmlDocument.AppendChild(oRootXmlNode);
    oXmlDocument.Save("XMLFileName.xml");

Это может быть плохой идеей, если фрагменты в конечном итоге окажутся огромными.

person Rastus7    schedule 20.05.2015