在 C# 中使用 LINQ 将 XML 解析为 类

Parsing XML into classes using LINQ in C#

我在将 XML 文档解析为我的自定义 classes 时遇到很多问题。我已经尝试阅读我可以在网上和此处找到的内容,但我仍然一无所获。我正在开发一个房地产应用程序,并且正在尝试为您拥有的基本 属性 建模:

我决定尝试将数据存储在一个xml文件中,我做了一个例子如下:

<?xml version="1.0" encoding="UTF-8"?>
<Property>
    <Name>Grove Center</Name>
    <Building>
        <Name>Building1</Name>
        <Tenant>
            <Name>Tenant1</Name>
            <SquareFeet>2300</SquareFeet>
            <Rent>34000</Rent>
        </Tenant>
        <Tenant>
            <Name>Tenant2</Name>
            <SquareFeet>3100</SquareFeet>
            <Rent>42000</Rent>
        </Tenant>
        <Tenant>
            <Name>Tenant3</Name>
            <SquareFeet>1700</SquareFeet>
            <Rent>29000</Rent>
        </Tenant>
    </Building>
    <Building>
        <Name>Building2</Name>
        <Tenant>
            <Name>Tenant1</Name>
            <SquareFeet>6150</SquareFeet>
            <Rent>80000</Rent>
        </Tenant>
        <Tenant>
            <Name>Tenant2</Name>
            <SquareFeet>4763</SquareFeet>
            <Rent>60000</Rent>
        </Tenant>
    </Building>
</Property>

实际上我的第一个问题是这种格式是否正确。我看到一些 xml 示例,他们在开始列出个人 [=16] 之前添加了一个额外的标签,例如 <buildings> =] 每个建筑物的标签。那有必要吗?我看到的 W3C 示例并没有那样做。但是 stackexchange 上的 post 与我所做的非常接近:Parsing XML with Linq with multiple descendants

这是我的 classes 在 C# 中的代码:

public class Property
{
    public string Name { get; set; }
    public List<Building> Buildings = new List<Building>();
}

public class Building
{
    public string Name { get; set; }
    public List<Tenant> Tenants = new List<Tenant>();
}

public class Tenant
{
    public string Name { get; set; }
    public int SF { get; set; }
    public decimal Rent { get; set; }
}

我不确定在 class 定义的列表中使用 new 关键字是否是一个好习惯。但是我在稍后尝试将建筑物或租户添加到列表中时遇到错误我的程序,所以我不知道还能做什么。现在我的主要代码只剩下:

Property p = new Property();
XDocument doc = XDocument.Load(@"C:\Users\SampleUser\Desktop\sample-property.xml");

感谢任何帮助,谢谢

以下查询将为您提供正确的结果:-

Property p = new Property
               {
                  Name = (string)doc.Root.Element("Name"),
                  Buildings = doc.Root.Elements("Building")
                                 .Select(x => new Building
                                  {
                                     Name = (string)x.Element("Name"),
                                     Tenants = x.Elements("Tenant")
                                                .Select(t => new Tenant
                                                 {
                                                     Name = (string)t.Element("Name"),
                                                     SF = (int)t.Element("SquareFeet"),
                                                     Rent = (decimal)t.Element("Rent")
                                                 }).ToList()
                                   }).ToList()
                };

您可能想要更改一些内容。

属性 名称必须与 xml 标签匹配,否则您必须手动指定映射。在您的示例代码中,建筑物和租户被声明为字段,您应该将其更改为属性。如果你愿意,你可以在构造函数中将它们初始化为空列表:

public class Property
{
    public string Name { get; set; }
    [XmlElement("Building")]
    public List<Building> Buildings { get; set; }

    public Property()
    {
        Buildings = new List<Building>();
    }
}

public class Building
{
    public string Name { get; set; }
    [XmlElement("Tenant")]
    public List<Tenant> Tenants { get; set; }

    public Building()
    {
        Tenants = new List<Tenant>();
    }
}

public class Tenant
{
    public string Name { get; set; }
    [XmlAttribute("SquareFeet")]
    public int SF { get; set; }
    public decimal Rent { get; set; }
}

此外,我建议反序列化文件而不是使用 linq。考虑这些辅助方法:

public static class XmlHelper
{
    public static T DeserializeFromXmlString<T>(string xml)
    {
        var xmlSerializer = new XmlSerializer(typeof (T));
        using (var stringReader = new StringReader(xml))
        {
            return (T) xmlSerializer.Deserialize(stringReader);
        }
    }

    public static T DeserializeFromXmlFile<T>(string filename) where T : new()
    {
        return DeserializeFromXmlString<T>(File.ReadAllText(filename));
    }
}

然后反序列化就很容易了:

var listOfProperties = XmlHelper.DeserializeFromXmlFile<Property>(@"C:\Users\SampleUser\Desktop\sample-property.xml");

用空列表初始化 public 字段是非常好的做法,可以避免出现错误。如果您不初始化它们,它们将为空,因此会出现错误。

但是,您可以使用属性而不是列表字段。

从 C# 6 开始,您可以使用简化的自动属性赋值:

public List<Building> Buildings {get;set;} = new List<Building>();

对于 C# < 6,您可以使用自动属性并在构造函数中初始化 属性 或使用带有支持字段的 属性。

//Auto property with assignment in constructor
public class Property
{
    public string Name { get; set; }
    public List<Building> Buildings {get;set;};
    public Property(){
        Buildings = new List<Building>();
    }
}

//Property with backing field
public class Property
{
    private List<Building> _buildings = new List<Building>();
    public string Name { get; set; }
    public List<Building> Buildings {get {return _buildings;} set {_buildings = value;}};
}

要读取 XML 和创建对象图,您可以将 LINQ 与对象初始化器结合使用。

Func<IEnumerable<XElement>, IEnumerable<Tenant>> getTenants = elements => {
    return elements.Select (e => new Tenant {
        Name = e.Element("Name").Value,
        Rent = decimal.Parse(e.Element("Rent").Value),
        SF = int.Parse(e.Element("SquareFeet").Value)
    });
};

Func<IEnumerable<XElement>, IEnumerable<Building>> getBuildings = elements => {
    return elements.Select (e => new Building{
        Name = e.Element("Name").Value,
        Tenants = getTenants(e.Elements("Tenant")).ToList()
    });
};

//xdoc is your parsed XML document
//e.g. var xdoc = XDdocument.Parse("xml contents here");
var property = new Property{
    Name = xdoc.Root.Element("Name").Value,
    Buildings = getBuildings(xdoc.Root.Elements("Building")).ToList()
};