Reputation: 682
Short version: I need to be able to serialize/deserialize XML and maintain the references based on custom Ids that I set. I need something that is generic and can do this by building the references back post serialization, or preferably customize a C# serializer would handle this.
I'm creating a WCF application that needs to communicate with a wide variety of applications. We are basically building a very complex calculator. We do have a common database, however, the state of the user's object could be different then the state in the DB and we don't want to store this intermediate state.
I need to be able to pass complex related objects in XML (see WCF) that utilize a customized reference builder. The builder needs to put the objects back together based on an Id that each object has. I want to be able to specify the id, so that another application (say C++) can call our application by building the XML properly.
I know that XmlSerializer will only copy the whole reference, and DataContractSerializer will maintain reference integrity, but it creates an identifier on the spot. How can I resolve these references either during serialization, or after serialization?
If the answer is "You can't" then I pose the following question.
Currently we have built a custom serializer, but it is SLOW (XmlSerializer is 8-30 times faster depending on file size). Our custom serializer can handle the following XML, but it has a second step where it resolves references (after deserialization). But I'm going to have to do some major rework and cusomization to get XmlSerializer to work with this, because we need the performance of XmlSerializer. (I.E. Using a list of ids and resolving those references with XmlIgnore on all of our reference classes). Are there any tools or libraries that already do this for XML either the resolution step or the serialization step?
Edit: When I deserialize this, I need these references to be maintained. If I change the teacher.Name within class it should change the teacher.Name in the school.
I've put together a simplified version of what I'm talking about and the XML I'd like to use to parse it.
public class Teacher
{
//This is a made up annotation that represents what I'd like to happen.
[DataMember(IsReferenceId = True)]
public int Id { get; set; }
public string Name { get; set; }
//This is a made up annotation that represents what I'd like to happen.
[DataMember(IsReference = True)]
public List<Class> Classes { get; set; }
public Teacher()
{
Classes = new List<Class>();
}
public Teacher(int id)
{
Classes = new List<Class>();
Id = id;
}
}
public class Class
{
//This is a made up annotation that represents what I'd like to happen.
[DataMember(IsReferenceId = True)]
public int Id { get; set; }
public string Subject { get; set; }
//This is a made up annotation that represents what I'd like to happen.
[DataMember(IsReference = True)]
public Teacher Teacher { get; set; }
public Class()
{}
public Class(int id)
{
Id = id;
}
}
public class School
{
public string Name { get; set; }
public List<Class> Classes { get; set; }
public List<Teacher> Teachers { get; set; }
}
I'm looking to be able to parse XML like this.
<School>
<Classes>
<Class>
<Id>1</Id>
<Subject>Biology</Subject>
<Teacher><Id>1</Id></Teacher>
</Class>
<Class>
<Id>2</Id>
<Subject>Advanced Biology</Subject>
<Teacher><Id>1</Id></Teacher>
</Class>
<Class>
<Id>3</Id>
<Subject>Algebra</Subject>
<Teacher><Id>2</Id></Teacher>
</Class>
<Class>
<Id>4</Id>
<Subject>Trigonometry</Subject>
<Teacher><Id>2</Id></Teacher>
</Class>
</Classes>
<Teachers>
<Teacher>
<Id>1</Id>
<Name>Biology Teacher</Name>
<Classes>
<Class><Id>1</Id></Class>
<Class><Id>2</Id></Class>
</Classes>
</Teacher>
<Teacher>
<Id>2</Id>
<Name>Biology Teacher</Name>
<Classes>
<Class><Id>3</Id></Class>
<Class><Id>4</Id></Class>
</Classes>
</Teacher>
</Teachers>
</School>
I haven't included any code that deserializes or serializes this, because the implementation will vary based on which serializer is used. But currently I'm using the XmlSerializer with [XmlIgnore] instead of [DataContract(IsReference = True)]. However, I'll need to put those references back in.
Note: JSON is not an option, but we can use any open source library that serializes Xml.
Upvotes: 1
Views: 2083
Reputation: 404
You mean something like Persist ?:
using System;
using elios.Persist;
using System.Collections.Generic;
using System.IO;
public class Program
{
public static void Main()
{
var students = new List<Student>();
students.Add(new Student {Name = "Alfred"});
students.Add(new Student {Name = "Ben"});
students.Add(new Student {Name = "Camila"});
students.Add(new Student {Name = "Denise"});
var alfred = students[0];
var ben = students[1];
var camila = students[2];
var denise = students[3];
alfred.AddFriend(ben);
alfred.AddFriend(camila);
ben.AddFriend(alfred);
ben.AddFriend(denise);
camila.AddFriend(alfred);
camila.AddFriend(ben);
camila.AddFriend(denise);
denise.AddFriend(camila);
var archive = new XmlArchive(typeof(List<Student>));
string xml;
using (var s = new MemoryStream())
{
archive.Write(s,students,"Students");
s.Position = 0;
using (var reader = new StreamReader(s))
{
xml = reader.ReadToEnd();
}
}
Console.WriteLine(xml);
}
}
public class Student
{
[Persist("Friends",IsReference = true, ChildName = "Friend")]
private readonly List<Student> m_friends;
public string Name { get; set; }
public Student()
{
m_friends = new List<Student>();
}
public void AddFriend(Student friend)
{
m_friends.Add(friend);
}
}
Produces:
<Students>
<Student Name="Alfred" id="4">
<Friends>
<Friend id="1" />
<Friend id="2" />
</Friends>
</Student>
<Student Name="Ben" id="1">
<Friends>
<Friend id="4" />
<Friend id="5" />
</Friends>
</Student>
<Student Name="Camila" id="2">
<Friends>
<Friend id="4" />
<Friend id="1" />
<Friend id="5" />
</Friends>
</Student>
<Student Name="Denise" id="5">
<Friends>
<Friend id="2" />
</Friends>
</Student>
</Students>
Upvotes: 1
Reputation: 34421
Here is serialization. Add the xml identification to line 1 of xml :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
XmlSerializer xs = new XmlSerializer(typeof(School));
XmlTextReader reader = new XmlTextReader(FILENAME);
School school = (School)xs.Deserialize(reader);
}
}
[XmlRoot("Teachers")]
public class Teachers
{
[XmlElement("Teacher")]
public List<Teacher> teacher { get; set; }
}
[XmlRoot("Teacher")]
public class Teacher
{
//This is a made up annotation that represents what I'd like to happen.
[XmlElement("Id")]
public int Id { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
//This is a made up annotation that represents what I'd like to happen.
[XmlElement("Classes")]
public List<Classes> Classes { get; set; }
}
[XmlRoot("Classes")]
public class Classes
{
[XmlElement("Class")]
public List<Class> c_class {get; set;}
}
[XmlRoot("Class")]
public class Class
{
//This is a made up annotation that represents what I'd like to happen.
[XmlElement("Id")]
public int Id { get; set; }
[XmlElement("Subject")]
public string Subject { get; set; }
//This is a made up annotation that represents what I'd like to happen.
[XmlElement("Teacher")]
public Teacher Teacher { get; set; }
}
[XmlRoot("School")]
public class School
{
public string Name { get; set; }
public Classes Classes { get; set; }
public Teachers Teachers { get; set; }
}
}
Upvotes: 0
Reputation: 34421
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication34
{
class Program
{
static void Main(string[] args)
{
string input =
"<School>" +
"<Classes>" +
"<Class>" +
"<Id>1</Id>" +
"<Subject>Biology</Subject>" +
"<Teacher><Id>1</Id></Teacher>" +
"</Class>" +
"<Class>" +
"<Id>2</Id>" +
"<Subject>Advanced Biology</Subject>" +
"<Teacher><Id>1</Id></Teacher>" +
"</Class>" +
"<Class>" +
"<Id>3</Id>" +
"<Subject>Algebra</Subject>" +
"<Teacher><Id>2</Id></Teacher>" +
"</Class>" +
"<Class>" +
"<Id>4</Id>" +
"<Subject>Trigonometry</Subject>" +
"<Teacher><Id>2</Id></Teacher>" +
"</Class>" +
"</Classes>" +
"<Teachers>" +
"<Teacher>" +
"<Id>1</Id>" +
"<Name>Biology Teacher</Name>" +
"<Classes>" +
"<Class><Id>1</Id></Class>" +
"<Class><Id>2</Id></Class>" +
"</Classes>" +
"</Teacher>" +
"<Teacher>" +
"<Id>2</Id>" +
"<Name>Biology Teacher</Name>" +
"<Classes>" +
"<Class><Id>3</Id></Class>" +
"<Class><Id>4</Id></Class>" +
"</Classes>" +
"</Teacher>" +
"</Teachers>" +
"</School>";
XDocument doc = XDocument.Parse(input);
var results = doc.Elements().Select(x => new {
classes = x.Element("Classes").Elements("Class").Select(y => new {
id = y.Element("Id").Value,
subject = y.Element("Subject").Value,
teacherId = y.Element("Teacher").Element("Id").Value
}).ToList(),
teachers = x.Element("Teachers").Elements("Teacher").Select(y => new {
id = y.Element("Id").Value,
name = y.Element("Name").Value,
classIds = y.Element("Classes").Elements("Class").Select(z => z.Element("Id").Value).ToList()
}).ToList()
}).FirstOrDefault();
}
}
}
Upvotes: 0