如何在 .NET 中反序列化为本地集合?
Posted
技术标签:
【中文标题】如何在 .NET 中反序列化为本地集合?【英文标题】:How Do I Deserialize Into Local Collections in .NET? 【发布时间】:2021-02-10 07:43:51 【问题描述】:我正在尝试在 .NET 中构建数据管道。我得到了一个xsd
并使用了XML Schema Definition Tool 来生成代表对象模型的C# 类。为了将此数据加载到我的数据存储中,我需要将来自 XML 的数据转换为与我的应用程序架构相匹配的结构并收集/重复数据删除元素。为此,我有一个解析器类,它将读取文件并将内容加载到本地集合中,然后将其加载到我的数据库中。我看到了两种选择 -
-
使用
XmlReader
手动循环XML 并提取我需要的数据,将其加载到流中的本地集合中。这是不可取的,因为它没有利用给我的强类型/严格的xsd
,并且需要很多硬编码的东西,比如while (reader.Read())
,检查特定的XML节点,然后`reader.GetAttribute("硬编码字符串")。
使用XmlSerializer
一次反序列化整个文件,然后循环遍历反序列化的集合并插入到我的本地集合中。这是不可取的,因为文件可能非常大,而且这种方法迫使我循环遍历所有数据两次(一次反序列化,一次将数据提取到我的本地集合)。
理想情况下,我希望通过某种方式注册要执行的委托,因为每个对象都被反序列化以插入到我的本地集合中。框架中有什么东西可以让我这样做吗?要求如下:
-
高性能 - 只循环一次数据。
功能性 - 在反序列化期间将数据插入到本地集合中。
可维护 - 利用通过
xsd
生成的强类型类。
我创建了一个最小的例子来说明我的观点。
示例 XML 文件:
<Hierarchy xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.example.com/example">
<Children>
<Child ChildId="1" ChildName="First">
<Parents>
<Parent ParentId="1" ParentName="First" RelationshipStart="1900-01-01T00:00:00"/>
<Parent ParentId="2" ParentName="Second" RelationshipStart="2000-01-01T00:00:00"/>
</Parents>
</Child>
<Child ChildId="2" ChildName="Second">
<Parents>
<Parent ParentId="2" ParentName="Second" RelationshipStart="1900-01-01T00:00:00"/>
<Parent ParentId="3" ParentName="Third" RelationshipStart="2000-01-01T00:00:00"/>
</Parents>
</Child>
</Children>
</Hierarchy>
我正在尝试加载的本地集合:
public Dictionary<int, string> Parents get;
public Dictionary<int, string> Children get;
public List<Relationship> Relationships get;
手动版本(不可维护且不使用xsd
):
public void ParseFileManually(string fileName)
using (var reader = XmlReader.Create(fileName))
while (reader.Read())
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Hierarchy")
while (reader.Read())
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Child")
int childId = int.Parse(reader.GetAttribute("ChildId"));
string childName = reader.GetAttribute("ChildName");
Children[childId] = childName;
while (reader.Read())
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Parent")
int parentId = int.Parse(reader.GetAttribute("ParentId"));
string parentName = reader.GetAttribute("ParentName");
DateTime relationshipStart = DateTime.Parse(reader.GetAttribute("RelationshipStart"));
Parents[parentId] = parentName;
Relationships.Add(
new Relationship
ParentId = parentId,
ChildId = childId,
Start = relationshipStart
);
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Child")
break;
反序列化版本(循环数据两次):
public void ParseFileWithDeserialize(string fileName)
var serializer = new XmlSerializer(typeof(Hierarchy));
using (var fileStream = new FileStream(fileName, FileMode.Open))
var fileData = (Hierarchy) serializer.Deserialize(fileStream);
foreach (var child in fileData.Children)
Children[child.ChildId] = child.ChildName;
foreach (var parent in child.Parents)
Parents[parent.ParentId] = parent.ParentName;
Relationships.Add(
new Relationship
ParentId = parent.ParentId,
ChildId = child.ChildId,
Start = parent.RelationshipStart
);
【问题讨论】:
这看起来像是***.com/questions/27365029/…的复制品 @StuartSmith 为什么你认为这是重复的?由于 OP 代码中的错误,链接的问题似乎是反序列化代码根本不起作用的问题。在我的问题中,我提供的代码工作正常,而是我试图了解是否有一种有效的方法来完成我想要做的事情。 我认为您采取了错误的方法。首先,您没有架构,因此要使用 xsd.exe,您需要创建架构。其次,您真正的目标是加载数据库,因此第一步是设计数据库中的表。设计表格后,您应该根据数据库的结构解析 xml。数据库中的表是二维的,xml 文件有两层以上。设计数据库时有两个目标 1) 速度 2) 大小。两者之间存在权衡。 你总是可以创建一个只有一张表的数据库,但你最终会复制数据。例如,如果您的父母有 4 个孩子。一列是父名称,它将为 4 个孩子中的每一个重复。因此,如果您是父表和子表,然后父名称只输入一次。但是,您需要加入父数据和子数据以获得关系,这需要更多时间来精确数据。 给出数据库结构。分两步做额外的工作。如果使用序列化,则首先创建以 xml 结构组织的 c# 类,然后必须转换为数据库结构。一步完成,解析xml,看起来像数据库结构,效率更高。 【参考方案1】:如果您使用这些定义,您应该使用一些注释从 XML 中的正确字段获取数据;
public class Hierarchy
public Hierarchy()
Children = new List<Child>();
public List<Child> Children get; set;
public class Child
public Child()
Parents = new List<Parent>();
[XmlAttribute("ChildId")]
public int ChildId get; set;
[XmlAttribute("ChildName")]
public string ChildName get; set;
public List<Parent> Parents get; set;
public class Parent
[XmlAttribute("ParentId")]
public int ParentId get; set;
[XmlAttribute("ParentName")]
public string ParentName get; set;
[XmlAttribute("RelationshipStart")]
public DateTime RelationshipStart get; set;
那么您应该能够将代码简化为;
public static Hierarchy Deserialize(string fileName)
using (var fileStream = new StreamReader(fileName, Encoding.UTF8))
XmlSerializer ser = new XmlSerializer(typeof(Hierarchy));
return (Hierarchy)ser.Deserialize(fileStream);
要对其进行测试,您可以创建一个示例数据集并将其序列化为文件,然后使用上述代码将其读回
public static void Serialize(Hierarchy h, string fileName)
System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(Hierarchy));
StreamWriter sw = new StreamWriter(fileName, false, Encoding.UTF8);
ser.Serialize(sw, h);
测试代码
static void Test()
Hierarchy h = new Hierarchy();
Parent p1 = new Parent() ParentId = 1, ParentName = "First", RelationshipStart = DateTime.Now ;
Parent p2 = new Parent() ParentId = 2, ParentName = "Second", RelationshipStart = DateTime.Now ;
Parent p3 = new Parent() ParentId = 3, ParentName = "Third", RelationshipStart = DateTime.Now ;
Child c1 = new Child() ChildId = 1, ChildName = "First" ;
c1.Parents.Add(p1);
c1.Parents.Add(p2);
Child c2 = new Child() ChildId = 2, ChildName = "Second" ;
c2.Parents.Add(p2);
c2.Parents.Add(p3);
h.Children.Add(c1);
h.Children.Add(c2);
Serialize(h, AppContext.BaseDirectory + "Text.xml");
Hierarchy hReadBack = Deserialize(AppContext.BaseDirectory + "Text.xml");
编辑:回答您的问题
使用这些类
public class Hierarchy
public Hierarchy()
Children = new List<Child>();
public List<Child> Children get; set;
private Dictionary<int, string> _parents;
private Dictionary<int, string> _childrenList;
private List<Relationship> _relationships;
private void CalcuateLists()
_parents = new Dictionary<int, string>();
_childrenList = new Dictionary<int, string>();
_relationships = new List<Relationship>();
foreach (Child c in this.Children)
if (!_childrenList.ContainsKey(c.ChildId))
_childrenList.Add(c.ChildId, c.ChildName);
foreach (Parent p in c.Parents)
if (!_parents.ContainsKey(p.ParentId))
_parents.Add(p.ParentId, p.ParentName);
if (_relationships.FirstOrDefault(dat => dat.ParentId == p.ParentId && dat.ChildId == c.ChildId) == null)
_relationships.Add(new Relationship() ChildId = c.ChildId, ParentId = p.ParentId, Start = p.RelationshipStart );
public Dictionary<int, string> Parents
get
if (_parents == null)
CalcuateLists();
return _parents;
public Dictionary<int, string> ChildrenList
get
if (_childrenList == null)
CalcuateLists();
return _childrenList;
public List<Relationship> Relationships
get
if (_relationships == null)
CalcuateLists();
return _relationships;
public class Child
public Child()
Parents = new List<Parent>();
[XmlAttribute("ChildId")]
public int ChildId get; set;
[XmlAttribute("ChildName")]
public string ChildName get; set;
public List<Parent> Parents get; set;
public class Parent
[XmlAttribute("ParentId")]
public int ParentId get; set;
[XmlAttribute("ParentName")]
public string ParentName get; set;
[XmlAttribute("RelationshipStart")]
public DateTime RelationshipStart get; set;
那么你的测试代码就变成了
public static void Test()
Hierarchy h = new Hierarchy();
Parent p1 = new Parent() ParentId = 1, ParentName = "First", RelationshipStart = DateTime.Now ;
Parent p2 = new Parent() ParentId = 2, ParentName = "Second", RelationshipStart = DateTime.Now ;
Parent p3 = new Parent() ParentId = 3, ParentName = "Third", RelationshipStart = DateTime.Now ;
Child c1 = new Child() ChildId = 1, ChildName = "First" ;
c1.Parents.Add(p1);
c1.Parents.Add(p2);
Child c2 = new Child() ChildId = 2, ChildName = "Second" ;
c2.Parents.Add(p2);
c2.Parents.Add(p3);
h.Children.Add(c1);
h.Children.Add(c2);
Serialize(h, AppContext.BaseDirectory + "Text.xml");
Hierarchy hReadBack = Deserialize(AppContext.BaseDirectory + "Text.xml");
Dictionary<int, string> Parents = hReadBack.Parents;
Dictionary<int, string> Children = hReadBack.ChildrenList;
List<Relationship> Relationships = hReadBack.Relationships;
编辑
不循环直接获取结果
你需要这个课程
public class Relationship
public int ParentId get; set;
public int ChildId get; set;
public DateTime Start get; set;
还有这个选择
// Get a list of child ids and names
Dictionary<int, string> Children = (from c in hReadBack.Children select new ChildId = c.ChildId, Name = c.ChildName).ToDictionary(dat => dat.ChildId, dat => dat.Name);
// Get a parent ids and names
Dictionary<int, string> Parents = (from p in hReadBack.Children.SelectMany(i => i.Parents) select new ParentId = p.ParentId, Name = p.ParentName ).Distinct().ToDictionary(dat => dat.ParentId, dat => dat.Name);
// Get the relationships
List<Relationship> Relationship = (from Child c in hReadBack.Children from Parent p in c.Parents select new Relationship() ChildId = c.ChildId, ParentId = p.ParentId, Start = p.RelationshipStart ).ToList();
【讨论】:
这没有回答问题。问题中的代码用于反序列化为Hierarchy
对象,问题是关于如何有效地反序列化为本地派生字典。
您可以通过一次计算所有三个列表来提高效率,然后将结果列表放入 Hierarchy 类中的局部变量中,然后让 get 返回局部变量。
这仍然不符合问题中提出的要求。 this.Children 中的 ForEach child 仍然涉及循环遍历数据两次,这违反了问题的要求 1。以上是关于如何在 .NET 中反序列化为本地集合?的主要内容,如果未能解决你的问题,请参考以下文章