我有一个类,它从数据库中获取数据,并以分层数据的形式获取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不包括结束标记</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
- 例如,SecurityGroup.Id可能自动生成为
<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;
}
}
我希望这能帮助其他人!