如何在 .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 中反序列化为本地集合?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以确定在 Json.Net 中反序列化了哪些属性? [复制]

在 C# 中序列化为 JSON 并在 TS 中反序列化

将对象序列化为 XElement 并在内存中反序列化

如何在 C# 中反序列化多个 JSON 对象?

如何在 asp.net 中反序列化 Json-Object

无论如何在 C# 中反序列化之前检查对象的类类型?