用类将xml转换为c#中的分层数据

本文关键字:分层 数据 xml 转换 | 更新日期: 2023-09-27 17:59:46

我有一个类,它从数据库中获取数据,并以分层数据的形式获取as-xml数据。

以下是数据示例:

<SecurityGroups>
<SecurityGroup>
    <Id>1</Id>
    <Name>View</Name>
</SecurityGroup>
<SecurityGroup>
    <Id>2</Id>
    <Name>Fill</Name>
    <SecurityUsers>
        <SecurityUser>
            <securityId>2</securityId>
            <userId>2</userId>
            <username>Fill</username>
        </SecurityUser>
        <SecurityUser>
            <securityId>2</securityId>
            <userId>3</userId>
            <username>FillOne</username>
        </SecurityUser>
        <SecurityUser>
            <securityId>2</securityId>
            <userId>4</userId>
        <username/></SecurityUser>
    </SecurityUsers>
</SecurityGroup>
<SecurityGroup>
    <Id>3</Id>
    <Name>Update</Name>
    <SecurityUsers>
        <SecurityUser>
            <securityId>3</securityId>
            <userId>5</userId>
            <username>Update</username>
        </SecurityUser>
        <SecurityUser>
            <securityId>3</securityId>
            <userId>6</userId>
            <username>UpdateOne</username>
        </SecurityUser>
    </SecurityUsers>
</SecurityGroup>
<SecurityGroup>
    <Id>4</Id>
    <Name>Admin</Name>
    <SecurityUsers>
        <SecurityUser>
            <securityId>4</securityId>
            <userId>1</userId>
            <username>JTays</username>
        </SecurityUser>
    </SecurityUsers>
</SecurityGroup>

效果很好!现在,我有一个类,它使用反射将xml转换为我最终将在树视图中使用的模型。下面是全班同学。

型号有:

[XmlRootAttribute(Namespace = "", IsNullable = false)]
public class SecurityGroup
{
    [XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
    public int Id { get; set; }
    [XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
    public string Name { get; set; }
    [XmlElementAttribute("SecurityUser",
        Form = XmlSchemaForm.Unqualified)]
    public List<SecurityUser> SecurityUsers { get; set; }
}
public class SecurityUser:IEntity
{
    [XmlElement(Form = XmlSchemaForm.Unqualified)]
    public int SecurityId { get; set; }
    [XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
    public int UserId { get; set; }
    [XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
    public string Username { get; set; }
    public int Id { get; set; }
}

这是转换的类。

    public class AdminManager : IAdminService
{
    public IEnumerable<SecurityGroup> SecurityGroups()
    {
        IEnumerable<SecurityGroup> list = null;
        XmlDocument xmlDoc = new XmlDocument();
        using (var c = new FMContext())
        {
            var xmlData = c.Database.SqlQuery<string>("AllSecurtyUsersProc").FirstOrDefault();
            if (xmlData != null)
            {
                xmlDoc.LoadXml(xmlData);
                list = ConvertXmlToClass<SecurityGroup>(xmlDoc, "/SecurityGroups/SecurityGroup");
            }
        }
        return list;
    }
    public static IEnumerable<T> ConvertXmlToClass<T>( XmlDocument doc, string nodeString)
        where T:class, new()
    {
        var xmlNodes = doc.SelectNodes(nodeString);
        List<T> list = new List<T>();
        foreach (XmlNode node in xmlNodes)
        {
            var item = GetNewItem<T>(node);
            list.Add(item);
        }
        return list;
    }
    public static T GetNewItem<T>(XmlNode node)
        where T:class, new()
    {
        var type = typeof (T);
        var item = new T();
        var properties = type.GetProperties();
        foreach (var property in properties)
        {
            var propertyType = property.PropertyType;
            var propertyName = property.Name;
            object value = null;
            if (IsEnumerable(property))
            {
                value = GetNodeCollectionValue(property,node);
            }
            else
            {
                value = GetNodeValue(node, propertyName);
            }
            if (value!=null)
            {
                property.SetValue(item, Convert.ChangeType(value, propertyType), null);
            }
        }
        return item;
    }
    private static object GetNodeCollectionValue(PropertyInfo property, XmlNode node)
    {
        var doc = new XmlDocument();
        var itemType = property.PropertyType.GenericTypeArguments[0];
        var xml = $"<{property.Name}><{itemType.Name}>{node.InnerXml}</{itemType.Name}></{property.Name}>";
        doc.LoadXml(xml);
        if (itemType != null)
        {
            var type = typeof (AdminManager);
            var methodInfo = type.GetMethod("ConvertXmlToClass");
            if (methodInfo!=null)
            {
                var method = methodInfo.MakeGenericMethod(itemType);
                if (method != null)
                {
                    object[] args = {doc, property.Name};
                    object result = method.Invoke(null, args);
                    return result;
                }
            }
        }
        return new object();
    }

    private static bool IsEnumerable(PropertyInfo property)
    {
        var type = property.PropertyType;
        return typeof (IEnumerable).IsAssignableFrom(type) && type.IsGenericType;
    }
    private static object GetNodeValue(XmlNode node, string nodeName)
    {
        var i = node[nodeName]?.InnerText;
        return i ?? null;
    }
}

好吧,现在是我的问题。我的问题是,当它转换时,它没有得到SecurtyUsers类的正确数据,它将它们作为对象添加,但所有内容都为null或0。有人能帮我弄清楚我去哪儿了吗?

用类将xml转换为c#中的分层数据

首先:示例XML不包括结束标记</SecurityGroups>

GetNodeCollectionValue中,您传递了property.Name,但它不应该是property.Name + "/" + itemType.Name吗?因为您想访问该节点的子节点?

此外,node.InnerXml不会被遍历,您会一次又一次地通过同一个节点。

我强烈建议使用.Net XML反序列化程序,而不是重新设计轮子。


为您的XML示例生成一个XML模式。然后从XSD创建.Net类。将XML从服务器传递到StringReader,然后使用该读取器进行反序列化。然后只从类实例访问SecurityGroup

更新以显示使用情况

手动或使用XSD.exe创建XML架构(您需要Windows SDK)。请参阅示例。

  • 创建一个sample.xml文件,内容就是您的示例
  • 找到并运行XSD工具xsd sample.xml /outputDir:XmlSerialization
  • 应该生成.xsd文件,您可能需要修改一些元素类型
    • 例如,SecurityGroup.Id可能自动生成为xs:string,将其更改为xs:int
    • 或者如果您知道属性总是设置的,请删除SecurityUser.userId中的minOccurs="0"
    • 您可能需要修复来自的SecurityGroup.SecurityUsers
<xs:element name="SecurityUsers" minOccurs="0" maxOccurs="unbounded">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="SecurityUser" minOccurs="0" maxOccurs="unbounded">

<xs:element name="SecurityUsers" minOccurs="0">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="SecurityUser" maxOccurs="unbounded">
  • 使用XSD xsd sample.xsd /outputDir:XmlSerialization /c运行XSD工具
  • 应该生成.cs文件,请验证
    • 我需要手动修复XSD,因为SecurityGroup.SecurityUser是作为二维数组生成的
  • 在项目/脚本中使用自动生成的类
  • 创建一个XmlSerializer(请参阅示例)并反序列化XML字符串,然后访问数据
SecurityGroups deserialized;
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(SecurityGroups));
using (var stringReader = new System.IO.StringReader(xmlData))
{
    deserialized = serializer.Deserialize(stringReader) as SecurityGroups;
}
  • 请在此处查看我手动修复的XSD,已验证使用您的示例数据

我发现了问题,因为我正在将整个xml传递到ConvertXmlToClass<T>方法中。我已经修复了这个问题,下面是工作代码。

public class AdminManager : IAdminService
{
    public IEnumerable<SecurityGroup> SecurityGroups()
    {
        IEnumerable<SecurityGroup> list = null;
        XmlDocument xmlDoc = new XmlDocument();
        using (var c = new FMContext())
        {
            var xmlData = c.Database.SqlQuery<string>("AllSecurtyUsersProc").FirstOrDefault();
            if (xmlData != null)
            {
                xmlDoc.LoadXml(xmlData);
                list = ConvertXmlToClass<SecurityGroup>(xmlDoc, "/SecurityGroups/SecurityGroup");
            }
        }
        return list;
    }
    public static IEnumerable<T> ConvertXmlToClass<T>(XmlDocument doc, string nodeString)
        where T : class, new()
    {
        var xmlNodes = doc.SelectNodes(nodeString);
        List<T> list = new List<T>();
        foreach (XmlNode node in xmlNodes)
        {
            var item = GetNewItem<T>(node);
            list.Add(item);
        }
        return list;
    }
    public static T GetNewItem<T>(XmlNode node)
        where T : class, new()
    {
        var type = typeof(T);
        var item = new T();
        var properties = type.GetProperties();
        foreach (var property in properties)
        {
            var propertyType = property.PropertyType;
            var propertyName = property.Name;
            object value = null;
            if (IsEnumerable(property))
            {
                value = GetNodeCollectionValue(property, node);
            }
            else
            {
                value = GetNodeValue(node, propertyName);
            }
            if (value != null)
            {
                property.SetValue(item, Convert.ChangeType(value, propertyType), null);
            }
        }
        return item;
    }
    private static object GetNodeCollectionValue(PropertyInfo property, XmlNode node)
    {
        var doc = new XmlDocument();
        var itemType = property.PropertyType.GenericTypeArguments[0];
        var xml = node.InnerXml;
        if (xml.Contains(property.Name))
        {
            var start = xml.IndexOf($"<{property.Name}>");
            var length = xml.IndexOf($"</{property.Name}>") - start + ($"</{property.Name}>").Length;
            xml = xml.Substring(start, length);
            doc.LoadXml(xml);
            if (itemType != null)
            {
                var type = typeof(AdminManager);
                var methodInfo = type.GetMethod("ConvertXmlToClass");
                if (methodInfo != null)
                {
                    var method = methodInfo.MakeGenericMethod(itemType);
                    if (method != null)
                    {
                        object[] args = { doc, $"/{property.Name}/{itemType.Name}" };
                        object result = method.Invoke(null, args);
                        var r = result as IEnumerable<object>;
                        return r;
                    }
                }
            }
        }
        return null;
    }

    private static bool IsEnumerable(PropertyInfo property)
    {
        var type = property.PropertyType;
        return typeof(IEnumerable).IsAssignableFrom(type) && type.IsGenericType;
    }
    private static object GetNodeValue(XmlNode node, string nodeName)
    {
        if (node[nodeName] != null)
        {
            var i = node[nodeName].InnerText;
            return i;
        }
        return null;
    }
}

我希望这能帮助其他人!