У меня возникли большие проблемы с переводом примеров, которые я нашел для использования с XML.
Начиная с этого (очень урезанная версия всего XML-файла)
<?xml version = "1.0" encoding = "UTF-8"?>
<MetadataSets>
<MetadataSet MetadataSetID = "999_never" >
<MetadataSetItem>
<TagName>NeverSeen</TagName>
<ItemSeq>10</ItemSeq>
</MetadataSetItem>
<MetadataSetItem>
<TagName>AlsoNeverSeen</TagName>
<ItemSeq>20</ItemSeq>
</MetadataSetItem>
</MetadataSet>
</MetadataSets>
Я пытаюсь создать упорядоченный список из двух полей, который, в конечном итоге, я надеюсь вернуть в виде списка кортежей.
'NeverSeen' 10
'AlsoNeverSeen' 20
Ниже приводится список нескольких подходов, которые я опробовал. В основном они включены для того, чтобы показать, что я приложил усилия, прежде чем обратиться за помощью.
Мне бы хотелось получить помощь, которая помогла бы мне найти правильный путь для решения этой конкретной проблемы, но также есть ли у кого-нибудь предложения по достойному, нетривиальному руководству по LINQ для XML.
Я должен сказать, что у меня есть опыт работы с базами данных; 30 секунд в SQL — 5 дней для XML и LINQ.
Я также путаюсь в разных подходах и, вероятно, непреднамеренно смешиваю их. например те, которые используют «от»
var schema3 = from el in XDoc.Elements("MetadataDomain")
where (String)el.Attribute("DomainUUID") == pDomainUUID
select (el);
затем повторите el
, даже если он только один.
Или те, которые не используют «от», а используют «.».
var schema4 = XDoc.Elements("MetadataDomain")
.Where(sch => (String)sch.Attribute("DomainUUID") == pDomainUUID)
.Select(sch => (String)sch.Element("SchemaName").Value);
тогда вам все равно придется перебирать результат, даже если он только один.
Я был в восторге от XPath, например
string schema5 = root.XPathSelectElement($"MetadataSets/MetadataDomain[@DomainUUID='{pDomainUUID}']").Element("SchemaName").Value;
Но я отказался от этого, потому что все это находится в одной строке, поэтому в случае сбоя нет возможности что-либо отладить. И почти все, что вы вводите, будет скомпилировано.
И как мне выбрать 2 «поля», например, некоторые говорят, что для выбора 2 «полей» используются анонимные типы, например
select new { car.Make, car.VIN}
when I try that with XML
where (String)el.Attribute("MetadataSetID") == pMetadataSetID
select new { el.Element("TagName").Value, el.Element("ItemSeq").Value } ;
Я получаю ошибку компиляции
Ошибка CS0833: анонимный тип не может иметь несколько свойств с одинаковым именем.
Кажется, компилятор не может отличить их друг от друга, и я не знаю, как присвоить им псевдонимы.
Спасибо Джей Си
Как обычно, я выстрелил себе в ногу, показав лишь отрывок информации. Фактически данные нормализуются как минимум в 2 «таблицы», представленные в XML. SchemaName(s), возможно, являются представлениями предметной области. MetadataSetID(ы) указывают подмножество и порядок этих элементов метаданных для конкретной пользовательской аудитории.
Результирующие данные, которые должны быть возвращены вверх по дереву, представляют собой пересечение этих данных для ОДНОГО имени схемы и ОДНОГО MetadataSetID. Как вы можете видеть, даже на этом раннем этапе задействовано множество «столбцов»/свойств. Например, гораздо больше, чем может выдержать словарь.
После вчерашнего разъяснения Юн Шуна я получил дальнейшее развитие, но затем застрял в применении фильтра для MetadataSetID, который был на 2 уровня выше тех тегов, которые я искал. Только что прояснив свои мысли о конечном состоянии, я попробую методы, предложенные Децием.
<!-- semi denormalised - what a database would look like
SchemaName TagName TagLabel TagType TagSize TagRepetative TagDelimiter
EHGS-Standard FamilyName Family Name string 255 true ;
EHGS-Standard BusinessName Business Name string 255 true ;
EHGS-Standard What What (Categories) string true ;
EHGS-Standard Where Where string true ;
EHGS-Standard When When string true ;
EHGS-Standard Contributor Contributor string true ;
EHGS-Standard Comments Comments string false ;
EHGS-Standard Title Title string true ;
EHGS-Standard Copyright Copyright string true ;
EHGS-Standard Subject Subject string true ;
EHGS-Standard Authors Authors string true ;
EHGS-Standard DocumentID Document ID string false ;
testPretendSchema Creator Creator string 255 true ;
testPretendSchema Reader Last Reader string 255 true ,
testPretendSchema What What (whatever) string true ;
MetadataSetID TagName ItemSeq TagScope
001_basic DocumentID 120 Restricted
001_basic FamilyName 10
001_basic BusinessName 20
001_basic What 30
001_basic Where 40
001_basic When 50
001_basic Comments 60
001_basic Title 70
001_basic Contributor 80
001_basic Copyright 90
001_basic Subject 100
001_basic Authors 110
999_neverTest NeverSeen 10
999_neverTest AlsoNeverSeen 20
-->
Я бы предложил создать классы C# из вашего XML (для этого есть онлайн-инструменты).
Сериализуйте XML в эти классы
Используйте Linq для получения результатов
[XmlRoot("MetadataSets")]
public class MetadataSets
{
[XmlElement("MetadataSet")]
public List<MetadataSet> MetadataSetList { get; set; }
}
public class MetadataSet
{
[XmlAttribute("MetadataSetID")]
public string MetadataSetID { get; set; }
[XmlElement("MetadataSetItem")]
public List<MetadataSetItem> MetadataSetItems { get; set; }
}
public class MetadataSetItem
{
[XmlElement("TagName")]
public string TagName { get; set; }
[XmlElement("ItemSeq")]
public int ItemSeq { get; set; }
}
public class Program
{
public static void Main()
{
var xml = @"<MetadataSets>
<MetadataSet MetadataSetID = ""999_never"">
<MetadataSetItem>
<TagName>NeverSeen</TagName>
<ItemSeq>10</ItemSeq>
</MetadataSetItem>
<MetadataSetItem>
<TagName>AlsoNeverSeen</TagName>
<ItemSeq>20</ItemSeq>
</MetadataSetItem>
</MetadataSet>
</MetadataSets>";
var serializer = new XmlSerializer(typeof(MetadataSets));
using var reader = new StringReader(xml);
var metadataSets = (MetadataSets)serializer.Deserialize(reader);
var items = metadataSets.MetadataSetList.SelectMany(x => x.MetadataSetItems);
foreach(var item in items)
{
Console.WriteLine($"{item.TagName} = {item.ItemSeq}");
}
}
}
Вот рабочий образец.
Результат:
это оказалось наиболее гибким решением, хотя извлекать данные из вложенных классов по-прежнему сложно. Для всех, кто наткнулся на этот пост, одним из уроков было то, что десериализация не может обрабатывать пустые теги, если только они не являются строковыми типами. т.е. <ItemSeq></ItemSeq> выйдет из строя с крайне бесполезной ошибкой «System.FormatException: входная строка '' была в неправильном формате».
Используйте Xml Linq с SortDictionary:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Data;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApp10
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
SortedDictionary<string, string> dict = new SortedDictionary<string,string>( doc.Descendants("MetadataSetItem")
.GroupBy(x => (string)x.Element("TagName"), y => (string)y.Element("ItemSeq"))
.ToDictionary(x => x.Key, y => y.FirstOrDefault()));
}
}
}
Пожалуйста, попробуйте следующее решение.
С#
void Main()
{
const string FILENAME = @"e:\Temp\jc508.xml";
XDocument xdoc = XDocument.Load(FILENAME);
var MetadataSet = xdoc.Descendants("MetadataSetItem")
.Select(val => new
{
TagName = val.Elements("TagName")?.SingleOrDefault().Value,
ItemSeq = val.Elements("ItemSeq")?.SingleOrDefault().Value,
}).ToList();
Console.WriteLine(MetadataSet);
}
Вы должны указать имя свойства в поле выбора:
select new { TagName = el.Element("TagName").Value, ItemSeq = el.Element("ItemSeq").Value }