Как выполнить xml-сериализацию ссылочных свойств в C#?

У меня есть сериализуемый класс Project, который, в том числе содержит как свойства две коллекции:

public  ObservableCollection<TypeEntity> Types {get; set}
ObservableCollection<ElementEntity> Elements{get; set}

Класс ElementEntity в свою очередь содержит свойство Type типа TypeEntity

public TypeEntity Type {get; set}

По факту все ссылочные свойства Type объектов класса ElementEntity содержатся в той самой коллекции Types.

При выполнении xml-сериализации в итоговом файле в узле каждого ElementEntity содержится полное раскрытие TypeEntity со всеми его полями. При обратной же десериализации, если изначально два разных элемента ссылались на один и тот же тип, то теперь они ссылаются на разные типы с одинаковыми свойствами, которые еще и не содержатся в десериализованной коллекции Types.

Есть ли какая-то возможность установить какие-то атрибуты для свойств, чтобы они сериализовывались/десериализовывались, не по набору свойству, а по какому-то ID или hash?

*Xml-сериализация по сути не обязательна, просто итоговый файл самый понятный и простая переносимость между разными ОС (windows/astraLinux).


Ответы (1 шт):

Автор решения: rotabor

С таким подходом - никак.

Решение задачи

Проблема тут: "в узле каждого ElementEntity содержится полное раскрытие TypeEntity со всеми его полями". Здесь как раз и нужно сохранять некий Type ID как ссылку на тип в коллекции типов.

Сериализация сохраняет только данные, но не значения указателей (ссылочных переменных). Поэтому ссылки нужно перенести в данные в виде ID и т. п. Это можно сделать на этапе сериализации.

Конечно, это негативно скажется на производительности, если речь идёт о больших массивах данных, но ID можно конвертивать обратно в ссылки на этапе десериализации.

Код, реализующий решение

Для кодирования использован ИИ DeepSeek.

Итак, с пятой попытки DeepSeek выдал код на запрос "I need a sample c# app with xml serialization and deserialization of the MyProject entity of the Project class. The Project class should contain two collections: one of Types and one of Entities. The Type class should have the Name property. The Entity class should have the Name property and the Type property which references the type. Enrich both collections with few items each. During serialization, the Type reference of Entity should be converted to a type ID to allow to recreate the reference during deserialization." (перевод не привожу, так как тут поставлена задача согласно решению выше):

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

public class Program {
    public static void Main() {
        // 1. Create sample project
        var project = CreateSampleProject();

        // 2. Serialize to XML
        string xml = Serialize(project);
        Console.WriteLine("Serialized XML:");
        Console.WriteLine(xml);

        // 3. Deserialize back
        Project deserialized = Deserialize(xml);

        // 4. Verify results
        VerifyDeserialization(project, deserialized);
    }

    private static Project CreateSampleProject() {
        var customerType = new Type { Name = "Customer" };
        var productType = new Type { Name = "Product" };
        var orderType = new Type { Name = "Order" };

        return new Project {
            Name = "MyProject",
            Types = new List<Type> { customerType, productType, orderType },
            Entities = new List<Entity>
            {
                new Entity { Name = "John Doe", Type = customerType },
                new Entity { Name = "Premium Subscription", Type = productType },
                new Entity { Name = "Order #1001", Type = orderType }
            }
        };
    }

    private static string Serialize(Project project) {
        var serializer = new XmlSerializer(typeof(Project));
        using (var writer = new StringWriter()) {
            serializer.Serialize(writer, project);
            return writer.ToString();
        }
    }

    private static Project Deserialize(string xml) {
        var serializer = new XmlSerializer(typeof(Project));
        using (var reader = new StringReader(xml)) {
            return (Project)serializer.Deserialize(reader);
        }
    }

    private static void VerifyDeserialization(Project original, Project deserialized) {
        Console.WriteLine("\nVerification Results:");
        Console.WriteLine($"Project Name Match: {original.Name == deserialized.Name}");

        Console.WriteLine("\nTypes:");
        foreach (var type in deserialized.Types) {
            Console.WriteLine($"- {type.Name}");
        }

        Console.WriteLine("\nEntities with Type References:");
        foreach (var entity in deserialized.Entities) {
            bool refValid = deserialized.Types.Contains(entity.Type);
            Console.WriteLine($"- {entity.Name} (Type: {entity.Type.Name}, RefValid: {refValid})");
        }
    }
}

[XmlRoot("Project")]
public class Project : IXmlSerializable {
    public string Name { get; set; }
    public List<Type> Types { get; set; } = new List<Type>();
    public List<Entity> Entities { get; set; } = new List<Entity>();

    public XmlSchema GetSchema() => null;

    public void ReadXml(XmlReader reader) {
        reader.ReadStartElement();
        Name = reader.ReadElementContentAsString("Name", "");

        // Read Types and build ID map
        var typeMap = new Dictionary<int, Type>();
        reader.ReadStartElement("Types");
        while (reader.IsStartElement("Type")) {
            int id = int.Parse(reader.GetAttribute("id"));
            string name = reader.GetAttribute("name");
            var type = new Type { Name = name };
            typeMap[id] = type;
            Types.Add(type);
            reader.ReadStartElement("Type");
        }
        reader.ReadEndElement();

        // Read Entities with type references
        reader.ReadStartElement("Entities");
        while (reader.IsStartElement("Entity")) {
            string name = reader.GetAttribute("name");
            int typeId = int.Parse(reader.GetAttribute("typeId"));
            Entities.Add(new Entity { Name = name, Type = typeMap[typeId] });
            reader.ReadStartElement("Entity");
        }
        reader.ReadEndElement();

        reader.ReadEndElement();
    }

    public void WriteXml(XmlWriter writer) {
        writer.WriteElementString("Name", Name);

        // Write Types with generated IDs
        writer.WriteStartElement("Types");
        var typeIds = new Dictionary<Type, int>();
        for (int i = 0; i < Types.Count; i++) {
            typeIds[Types[i]] = i + 1;
            writer.WriteStartElement("Type");
            writer.WriteAttributeString("id", (i + 1).ToString());
            writer.WriteAttributeString("name", Types[i].Name);
            writer.WriteEndElement();
        }
        writer.WriteEndElement();

        // Write Entities with type references
        writer.WriteStartElement("Entities");
        foreach (var entity in Entities) {
            writer.WriteStartElement("Entity");
            writer.WriteAttributeString("name", entity.Name);
            writer.WriteAttributeString("typeId", typeIds[entity.Type].ToString());
            writer.WriteEndElement();
        }
        writer.WriteEndElement();
    }
}

public class Type {
    public string Name { get; set; }

    public override bool Equals(object obj) => obj is Type t && Name == t.Name;
    public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}

public class Entity {
    public string Name { get; set; }
    public Type Type { get; set; }
}

Результат:

Serialized XML:
<?xml version="1.0" encoding="utf-16"?>
<Project>
  <Name>MyProject</Name>
  <Types>
    <Type id="1" name="Customer" />
    <Type id="2" name="Product" />
    <Type id="3" name="Order" />
  </Types>
  <Entities>
    <Entity name="John Doe" typeId="1" />
    <Entity name="Premium Subscription" typeId="2" />
    <Entity name="Order #1001" typeId="3" />
  </Entities>
</Project>

Verification Results:
Project Name Match: True

Types:
- Customer
- Product
- Order

Entities with Type References:
- John Doe (Type: Customer, RefValid: True)
- Premium Subscription (Type: Product, RefValid: True)
- Order #1001 (Type: Order, RefValid: True)

ЗАМЕЧАНИЕ

В этом ответе только код, который иллюстрирует решение, сгенерирован с помощью ИИ, в то время как алгоритм решения является авторским.

P. S.

Поскольку в документации нет (есть) примера использования DataContractSerializer, то приведу пример от DeepSeek: "I need a sample c# app with xml serialization and deserialization of the MyProject entity of the Project class. The Project class should contain two collections: one of Types and one of Entities. The Type class should have the Name property. The Entity class should have the Name property and the Type property which references the type. Enrich both collections with few items each. Use the DataContractSerializer class".

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

namespace XmlSerializationSample {
    [DataContract(Name = "Project", Namespace = "")]
    public class Project {
        [DataMember]
        public List<EntityType> Types { get; set; } = new List<EntityType>();
        [DataMember]
        public List<Entity> Entities { get; set; } = new List<Entity>();
        public static void SerializeToFile(string filePath, Project project) {
            var serializer = new DataContractSerializer(typeof(Project));
            var settings = new XmlWriterSettings { Indent = true };
            using (var writer = XmlWriter.Create(filePath, settings)) {
                serializer.WriteObject(writer, project);
            }
        }
        public static Project DeserializeFromFile(string filePath) {
            var serializer = new DataContractSerializer(typeof(Project));
            using (var reader = XmlReader.Create(filePath)) {
                return (Project)serializer.ReadObject(reader);
            }
        }
    }

    [DataContract(Name = "Type", Namespace = "")]
    public class EntityType {
        [DataMember]
        public string Name { get; set; }
        public EntityType() { }
        public EntityType(string name) { Name = name; }
    }

    [DataContract(Name = "Entity", Namespace = "")]
    public class Entity {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public EntityType Type { get; set; }
        public Entity() { }
        public Entity(string name, EntityType type) { Name = name; Type = type; }
    }

    class Program {
        static void Main(string[] args) {
            // Create a sample project
            var project = new Project();
            // Create types
            var personType = new EntityType("Person");
            var productType = new EntityType("Product");
            var orderType = new EntityType("Order");
            project.Types.AddRange(new[] { personType, productType, orderType });
            // Create entities
            project.Entities.Add(new Entity("Customer", personType));
            project.Entities.Add(new Entity("Employee", personType));
            project.Entities.Add(new Entity("Book", productType));
            project.Entities.Add(new Entity("PurchaseOrder", orderType));
            // Serialize to XML
            string filePath = "project.xml";
            Project.SerializeToFile(filePath, project);
            Console.WriteLine($"Project serialized to {filePath}");
            // Deserialize from XML
            var deserializedProject = Project.DeserializeFromFile(filePath);
            Console.WriteLine("\nDeserialized project contents:");
            Console.WriteLine("\nTypes:");
            foreach (var type in deserializedProject.Types) {
                Console.WriteLine($"- {type.Name}");
            }
            Console.WriteLine("\nEntities:");
            foreach (var entity in deserializedProject.Entities) {
                Console.WriteLine($"- {entity.Name} (Type: {entity.Type.Name})");
            }
            Console.WriteLine("\nPress any key to exit...");
            Console.ReadKey();
        }
    }
}

Результат:

Project serialized to project.xml

Deserialized project contents:

Types:
- Person
- Product
- Order

Entities:
- Customer (Type: Person)
- Employee (Type: Person)
- Book (Type: Product)
- PurchaseOrder (Type: Order)
→ Ссылка