2014-12-27 3 views
3

[Java или C#] У меня есть некоторые проблемы с сериализацией. Как не дублировать всю информацию об объекте и использовать только ссылку?Ссылка на сериализацию XML - дубликат

Примеры классов:

class Author { 
    public String id; 
    public String name; 
} 

class Book { 
    public String id; 
    public Author author; 
    public String title; 
} 

И я должен форматировать выходной файл, как здесь:

<store> 
    <authors> 
    <author id="PK"> 
    <name>Philip Kindred</name> 
    </author> 
    </authors> 

    <books> 
    <book id="u1"> 
     <author>PK</author> <!-- use only ID --> 
     <title>Ubik</title> 
    </book> 
    </books> 
</store> 
+2

Не могли бы вы объяснить, что вы имеете в виду под «дублировать всю информацию об объекте»? Вы имеете в виду, «как я могу избежать ручного кодирования XML-вывода для каждого поля в моих классах»? Если это так, в C# вы можете использовать ['XmlSerializer'] (http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx) автоматически сериализовать и десериализовать из XML с помощью отражения. – dbc

+0

Это не проблема. В инструкции книги я не хочу дублировать каждый авторский объект, но ссылаюсь на одиночный (например, по идентификатору автора). – Tomiak

+1

Проблема в руке - это ясно, как сохранить связь агрегации в XML. Учитывая иерархический характер XML (очень хороший способ выразить композицию, но не агрегацию), которая всегда требует дополнительной логики, чтобы заставить ее работать, см. Один возможный ответ ниже. –

ответ

2

Вы столкнулись с проблемой представления агрегации вместо композиции отношений в XML. Сериализация XML чрезвычайно проста, когда вы сохраняете отношения родитель-потомок, когда родитель владеет дочерним элементом (т. Е. Композицией). В этом случае книга имеет одного автора (или более), но не владеет ею, потому что один автор может быть автором многих других книг.

В этом случае вы можете сделать что-то похожее на то, что сделано в базах данных, то есть иметь две отдельные записи и выразить отношения через внешний ключ. Смотрите пример ниже:

[Serializable] 
public class Author 
{ 
    [XmlAttribute("id")] 
    public String Id { get; set; } 
    [XmlElement("name")] 
    public String Name { get; set; } 
} 

[Serializable] 
public class Book 
{ 
    private Author _author; 

    [XmlIgnore] 
    public Author Author 
    { 
     get { return _author; } 
     set 
     { 
      _author = value; 
      AuthorId = _author != null ? _author.Id : null; 
     } 
    } 

    [XmlAttribute("id")] 
    public String Id { get; set; } 

    [XmlElement("author")] 
    public String AuthorId { get; set; } 

    [XmlElement("title")] 
    public String Title { get; set; } 
} 

[Serializable] 
public class Store 
{ 
    [XmlArray("authors")] 
    [XmlArrayItem("author", Type = typeof(Author))] 
    public List<Author> Authors { get; set; } 

    [XmlArray("books")] 
    [XmlArrayItem("book", Type = typeof(Book))] 
    public List<Book> Books { get; set; } 

    public Store() 
    { 
     Books = new List<Book>(); 
     Authors = new List<Author>(); 
    } 
} 


internal class Program 
{ 
    private static void Main(string[] args) 
    { 
     // Create some authors 
     var authors = new List<Author> 
     { 
      new Author{Id="PK", Name="Philip Kindred"}, 
      new Author{Id="WS", Name="William Shakespeare"}, 
     }; 

     // Create some books linked to the authors 
     var books = new List<Book> 
     { 
      new Book{Id = "U1", Author = authors[0], Title = "Do Androids Dream of Electric Sheep?"}, 
      new Book{Id = "U2", Author = authors[1], Title = "Romeo and Juliet"} 
     }; 

     var store = new Store {Authors = authors, Books = books}; 

     var success = Serialiser.SerialiseToXml(store, "store.xml"); 

     // Deserialize the data from XML 
     var store2 = Serialiser.DeserialseFromXml<Store>("store.xml"); 

     // Resolve the actual Author instances from the saved IDs (foreign key equivalent in databases) 
     foreach (var book in store2.Books) 
      book.Author = store2.Authors.FirstOrDefault(author => author.Id == book.AuthorId); 

     // Now variable 'store' and 'store2' have the same equivalent data 
    } 
} 

// Helper class to serialize and deserialize the data to XML file 
public static class Serialiser 
{ 
    public static bool SerialiseToXml(object obj, string filename) 
    { 
     try 
     { 
      var ws = new XmlWriterSettings 
      { 
       NewLineHandling = NewLineHandling.Entitize, 
       NewLineChars = Environment.NewLine, 
       Indent = true, 
       NewLineOnAttributes = false 
      }; 
      var xs = new XmlSerializer(obj.GetType()); 
      using (var writer = XmlWriter.Create(filename, ws)) 
       xs.Serialize(writer, obj); 

      return true; 
     } 
     catch(Exception ex) 
     { 
      return false; 
     } 
    } 

    public static T DeserialseFromXml<T>(string filename) where T : new() 
    { 
     var typeofT = typeof(T); 
     try 
     { 
      var xs = new XmlSerializer(typeofT); 
      using (var reader = XmlReader.Create(filename)) 
       return (T)xs.Deserialize(reader); 
     } 
     catch(Exception ex) 
     { 
      return default(T); 
     } 
    } 
} 

'store.xml' будет выглядеть следующим образом:

<?xml version="1.0" encoding="utf-8"?> 
<Store xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
<authors> 
    <author id="PK"> 
    <name>Philip Kindred</name> 
    </author> 
    <author id="WS"> 
    <name>William Shakespeare</name> 
    </author> 
</authors> 
<books> 
    <book id="U1"> 
    <author>PK</author> 
    <title>Do Androids Dream of Electric Sheep?</title> 
    </book> 
    <book id="U2"> 
    <author>WS</author> 
    <title>Romeo and Juliet</title> 
    </book> 
</books> 
</Store> 
1

C# ответ

Вы можете сделать это, если у вас есть какой-то больший класс контейнера, может управлять перекрестными ссылками между объектами. В вашем случае у вас, похоже, есть объект Store, который может служить этой цели. Store поддерживает словари книг и авторов по названию; Book запоминает id своего автора, а фактическое Author выдается из магазина по мере необходимости. Разумеется, для этого требуется как Author, так и Book знать магазин, в котором они существуют.

Пример реализации может выглядеть следующим образом:

public class Author 
{ 
    string id; 
    Store store; 

    [XmlIgnore] 
    public Store Store { 
     get { 
      return store; 
     } 
     set { 
      if (store != null && id != null) 
       store.Authors.Remove(id); 
      this.store = value; 
      if (store != null && id != null) 
       store.Authors[id] = this; 
     } 
    } 

    [XmlAttribute("id")] 
    public string Id 
    { 
     get 
     { 
      return id; 
     } 
     set 
     { 
      if (store != null && id != null) 
       store.Authors.Remove(id); 
      this.id = value; 
      if (store != null && id != null) 
       store.Authors[id] = this; 
     } 
    } 

    [XmlElement("name")] 
    public string Name { get; set; } 
} 

public class Book 
{ 
    string authorId; 
    string id; 
    Store store; 

    [XmlIgnore] 
    public Store Store 
    { 
     get 
     { 
      return store; 
     } 
     set 
     { 
      if (store != null && id != null) 
       store.Books.Remove(id); 
      this.store = value; 
      if (store != null && id != null) 
       store.Books[id] = this; 
     } 
    } 

    [XmlAttribute("id")] 
    public string Id 
    { 
     get 
     { 
      return id; 
     } 
     set 
     { 
      if (store != null && id != null) 
       store.Books.Remove(id); 
      this.id = value; 
      if (store != null && id != null) 
       store.Books[id] = this; 
     } 
    } 

    [XmlElement("author")] 
    public string AuthorID 
    { 
     get 
     { 
      return authorId; 
     } 
     set 
     { 
      authorId = value; 
     } 
    } 

    [XmlIgnore] 
    public Author Author 
    { 
     get 
     { 
      if (store == null) 
       return null; 
      if (AuthorID == null) 
       return null; 
      return store.Authors[AuthorID]; 
     } 
     set 
     { 
      if (value == Author) 
       return; 
      if (value == null) 
      { 
       authorId = null; 
      } 
      else 
      { 
       if (value.Id == null) 
        throw new ArgumentException(); 
       authorId = value.Id; 
      } 

      AssertCorrectAuthor(value); 
     } 
    } 

    [Conditional("DEBUG")] 
    private void AssertCorrectAuthor(Author author) 
    { 
     if (store != null) 
      Debug.Assert(author == Author); 
    } 

    [XmlElement("title")] 
    public string Title { get; set; } 
} 

[XmlRoot("store")] 
public class Store 
{ 
    readonly Dictionary<string, Book> books = new Dictionary<string, Book>(); 
    readonly Dictionary<string, Author> authors = new Dictionary<string, Author>(); 

    [XmlIgnore] 
    public IDictionary<string, Book> Books 
    { 
     get 
     { 
      return books; 
     } 
    } 

    [XmlIgnore] 
    public IDictionary<string, Author> Authors 
    { 
     get 
     { 
      return authors; 
     } 
    } 

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] 
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] 
    [XmlArray("authors")] 
    [XmlArrayItem("author")] 
    public Author[] AuthorList // proxy array for serialization. 
    { 
     get 
     { 
      return Authors.Values.ToArray(); 
     } 
     set 
     { 
      foreach (var author in authors.Values) 
      { 
       author.Store = null; 
      } 
      Authors.Clear(); 
      if (value == null) 
       return; 
      foreach (var author in value) 
      { 
       author.Store = this; 
      } 
     } 
    } 

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] 
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] 
    [XmlArray("books")] 
    [XmlArrayItem("book")] 
    public Book[] BookList // proxy array for serialization. 
    { 
     get 
     { 
      return Books.Values.ToArray(); 
     } 
     set 
     { 
      foreach (var book in Books.Values) 
      { 
       book.Store = null; 
      } 
      Books.Clear(); 
      if (value == null) 
       return; 
      foreach (var book in value) 
      { 
       book.Store = this; 
      } 
     } 
    } 
} 

И, тест:

public static class TestStore 
{ 
    public static void Test() 
    { 
     string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?> 
<store> 
    <authors> 
    <author id=""PK""> 
    <name>Philip Kindred</name> 
    </author> 
    </authors> 

    <books> 
    <book id=""u1""> 
     <author>PK</author> <!-- use only ID --> 
     <title>Ubik</title> 
    </book> 
    <book id=""t1""> 
     <author>PK</author> <!-- use only ID --> 
     <title>The Transmigration of Timothy Archer</title> 
    </book> 
    </books> 
</store> 
"; 
     var store = xml.LoadFromXML<Store>(); 
     Debug.Assert(store.BookList[0].Author == store.AuthorList[0]); // no assert 
     Debug.Assert(store.BookList[1].Author == store.AuthorList[0]); // no assert; verify that all books use the same instance of the `Author` class. 
    } 
} 
Смежные вопросы