Dot Net Thoughts

January 19, 2008

Serializing with WCF

Filed under: csharp,Uncategorized — dotnetthoughts @ 1:23 pm
Tags: , , ,

First of all, I want to apologize for the amount of time it has taken me to put up a new blog post. I typically try to write once a week, but my life has been crazy as of late. The outcome of all of the madness is that I will be packing up my family and moving from Portland, Oregon to Denver, Colorado at the end of this month. (My wife has been accepted into a Master’s degree program there!)Because of this, posts for the next couple of months may be sporadic, as well. (I’m looking for work in the Denver area. If anybody has any leads, let me know… )

In celebration of my move, I thought that I would put together an example that demonstrates two different ways that WCF serializes objects. (This posting will simply show different ways the engine can work. I hope to actually wire it into the WCF pipeline in a future post.) Since I spent last night packing all of my books into boxes, I thought that maybe I’d find a way to organize them. The goal of this application is to create a manifest list allows me quickly find any book that I’ve packed away in a moving box.

As always, the complete code is at the end of the blog post. The amount of code with this post is a little bit greater than usual. I’ll try and write code directly related to the topic within the text, but I would recommend copying the code to studio if you want to follow along.

Since my main objective is to manage books and boxes, I’m going to begin by simply creating a very basic object for each. My Book object will contain two different properties (Title and Author), and my Box object will simply contain an Id. Since we will be using the WCF engine to do our serialization we will need to mark the classes and properties we wish to expose to the serialization engine with the [DataContract] and the [DataMember] attributes.

 
    #region Book Class            

    [DataContract] 
    public class Book 
    { 
        [DataMember] 
        public string Title { get; set; }            

        [DataMember] 
        public string Author { get; set; } 
    } 
    #endregion            

    #region Box Class 
    [DataContract] 
    public class Box 
    { 
        [DataMember] 
        public int Id { get; set; } 
    } 
   #endregion 

I now need a method to associate books with boxes. In order to keep my Book and Box object as loosely coupled as possible, I will add a Manifest object to the project which will be responsible for maintaining the relationship between the two. The Manifest object will expose three properties. The Books property will contain a collection of all of the Books on my bookshelf. The Boxes property will contain all of the Boxes I’m using to move. The LineItems property will contain information as to which book is in which box.

 
        //Property inside of the Manifest Class. 
        private List<Box> _boxes = new List<Box>(); 
        [DataMember] 
        public List<Box> Boxes 
        { 
            get 
            { 
                return _boxes; 
            } 
            private set 
            { 
                _boxes = value; 
            } 
        } 
        //LineItems and Books here… 

Since we are using WCF, the serializer will require both a getter and a setter for every object it serializes. I don’t really want to give consumers of my class the ability to blow away my boxes collection, so I have explicitly created a private setter. This allows WCF the access it needs, while denying direct access to other consumers.

LineItems are added to the collection by calling the AddLineItem method. This method adds the book to the book collection, adds a box to the box collection, and creates a relationship between the two by adding a new LineItem to the LineItems collection. It is important to note that the association created in the LineItems collection does not contain new instances of books and boxes, rather it contains a reference to the master copy in the Books and Boxes collection. (In other words, the association is by reference.)

 
        public void AddLineItem(Book book, Box box) 
        { 
            Books.Add(book); 
            Boxes.Add(box); 
            LineItems.Add(new LineItem(book, box)); 
        } 

I’ve overridden the ToString on the manifest object to display the complete list of books and boxes that it contains. There is also a CreateShippingManifest method which will be used to create a generic manifest of three books contained in two boxes. (See code below.)

To test our code, we’ll create our sample manifest and view the results.

 
      Manifest manifest = CreateShippingManifest(); 
      Console.WriteLine(manifest.ToString()); 

The results which come back are as follows:

 
        Box 1   A Brief History of Time 
        Box 1   Guards Guards 
        Box 2   The Reptile Room 

Now, we’ll make an assumption that I made a mistake in entering a box label into the program. Instead of box 1, I meant the Id to be box 100. We’ll make the change to the box id, and the manifest will automatically pick up the results.

 
        Manifest manifest = CreateShippingManifest(); 
        manifest.Boxes[0].Id = 100; 
        Console.WriteLine(manifest.ToString()); 

Results:

 
        Box 100 A Brief History of Time 
        Box 100 Guards Guards 
        Box 2   The Reptile Room

Now, let’s add some code to serialize and deserialize our manifest. This is actually a fairly simple process. The first method will serialize the data to disc (as Xml), the second method will read the file and deserialize it into a new object.

 
      public void Serialize(string fileName) 
      { 
        DataContractSerializer ds = new DataContractSerializer(this.GetType());            

         using (Stream stream = File.Create(fileName)) 
         { 
             ds.WriteObject(stream, this); 
         } 
      }   

      public void Deserialize(string fileName) 
      { 
          using (Stream stream = File.OpenRead(fileName)) 
          { 
              DataContractSerializer ds = new DataContractSerializer(this.GetType()); 
              Manifest manifest = (Manifest)ds.ReadObject(stream); 
              this.Books = manifest.Books; 
              this.Boxes = manifest.Boxes; 
              this.LineItems = manifest.LineItems; 
          } 
      } 

We can now run code similar to that we ran above. Between steps, let’s serialize and deserialize our objects, though.

 
      static void Main(string[] args) 
      { 
          string serializationFile = @"c:\tempmw\Manifest.xml";            

          Manifest manifest = CreateShippingManifest(); 
          manifest.Serialize(serializationFile);            

          Manifest manifest2 = new Manifest(); 
          manifest2.Deserialize(serializationFile);            

          Console.WriteLine("Object after deserialization"); 
          Console.WriteLine(manifest2.ToString());            

          manifest2.Boxes[0].Id = 100; 
          Console.WriteLine(manifest2.ToString());            

          Console.ReadLine(); 
      } 

This yields the results:

 
      Object after deserialization 
      Box 1   A Brief History of Time 
      Box 1   Guards Guards 
      Box 2   The Reptile Room            

      Box 1   A Brief History of Time 
      Box 1   Guards Guards 
      Box 2   The Reptile Room 

But wait a minute! What happened here? We clearly changed the Id of the first box to be 100, yet the results still state that our box is Box 1! The code looks very similar to what we used in the non-serialized objects. The reason that this happens is because of the way the default instance of the DataContractSerializer writes out the results. If you take a look at the Xml file that is created from our serializer, you’ll see results similar to the following:

 
<Books> 
  <Book> 
    <Author>Stephen Hawking</Author> 
    <Title>A Brief History of Time</Title> 
  </Book> 
  … 
</Books> 
<Boxes> 
  <Box> 
    <Id>1</Id> 
  </Box> 
  … 
</Boxes> 
<LineItems> 
  <Manifest.LineItem> 
    <Book> 
      <Author>Stephen Hawking</Author> 
      <Title>A Brief History of Time</Title> 
    </Book> 
    <Box> 
      <Id>1</Id> 
    </Box> 
  </Manifest.LineItem> 
  … 
</LineItems>  

The Xml does not contain the associations between the manifest items and the books and boxes that we set up so carefully in our code. Changing the box Id in the master collection no longer changes the box id in all of the children.

We can very easily fix this by updating the DataContractSerializer instantiation in our Serialize method. The important parameters in the new constructor are the third and fifth. The third parameter (maxItemsInObjectGraph) indicates the total number of objects that the Xml can contain. If the number of objects in the Xml is exceeded, an error will be raised. The fifth parameter (preserveObjectReferences) indicates that the associations between objects should be preserved.

 
        public void Serialize(string fileName) 
        { 
            //DataContractSerializer ds = new DataContractSerializer(this.GetType());            

            DataContractSerializer ds = new DataContractSerializer(this.GetType(), null, 100, false, true, null);            

            using (Stream stream = File.Create(fileName)) 
            { 
                ds.WriteObject(stream, this); 
            } 
        } 

The important parameters in the new constructor are the third and fifth. The third parameter (maxItemsInObjectGraph) indicates the total number of objects that the Xml can contain. If the number of objects in the Xml is exceeded, an error will be raised. The fifth parameter (preserveObjectReferences) indicates that the associations between objects should be preserved. Now, when we rerun our demo, we see that the Id of the box is correctly updated.

 
      Object after deserialization 
      Box 1   A Brief History of Time 
      Box 1   Guards Guards 
      Box 2   The Reptile Room            

      Box 100 A Brief History of Time 
      Box 100 Guards Guards 
      Box 2   The Reptile Room 

Examining the Xml now shows the Id and IdRef structures in place to reassociate the data.

 
<Manifest z:Id="1">        

<Books z:Id="2" z:Size="3"> 
   <Book z:Id="3"> 
     <Author z:Id="4">Stephen Hawking</Author> 
     <Title z:Id="5">A Brief History of Time</Title> 
   </Book> 
   ... 
</Books> 
<Boxes z:Id="12" z:Size="3"> 
   <Box z:Id="13"> 
      <Id>1</Id> 
   </Box> 
   ... 
</Boxes> 
<LineItems z:Id="15" z:Size="3"> 
   <Manifest.LineItem z:Id="16"> 
      <Book z:Ref="3" i:nil="true"/> 
      <Box z:Ref="13" i:nil="true"/> 
   </Manifest.LineItem> 
   ... 
   </LineItems> 
</Manifest> 

So, there you have it. With WCF you can serialize by value or by reference. Pretty neat stuff. This has kind of been a marathon post, I hope you could follow along. Let me know if you find anything that isn’t clear!
Code Safe!
MW

 
---------------------Sample Code-------------------------------------- 
//Console Application 
using System; 
using WcfSample;        

namespace PersistApp 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            //You will need to change the filepath to a file on your local machine. 
            string serializationFile = @"c:\tempmw\Manifest.xml"; 
            //*********************************************************************        

            Manifest manifest = CreateShippingManifest();        

            Console.WriteLine("Manifest before serializing...\n"); 
            Console.WriteLine(manifest.ToString());        

            manifest.Serialize(serializationFile);        

            Manifest manifest2 = new Manifest(); 
            manifest2.Deserialize(serializationFile);        

            Console.WriteLine("Object after deserialization"); 
            Console.WriteLine(manifest2.ToString());        

            manifest2.Boxes[0].Id = 100; 
            Console.WriteLine(manifest2.ToString());        

            Console.ReadLine(); 
        }        

        private static Manifest CreateShippingManifest() 
        { 
            Manifest manifest = new Manifest();        

            Book book1 = new Book { Title = "A Brief History of Time", Author = "Stephen Hawking" }; 
            Book book2 = new Book { Title = "Guards Guards", Author = "Terry Pratchett" }; 
            Book book3 = new Book { Title = "The Reptile Room", Author = "Lemony Snicket" };        

            Box box1 = new Box { Id = 1 }; 
            Box box2 = new Box { Id = 2 };        

            manifest.AddLineItem(book1, box1); 
            manifest.AddLineItem(book2, box1); 
            manifest.AddLineItem(book3, box2);        

            return manifest; 
        }        

    } 
}        

//*************************************************************        

//Classes        

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

namespace WcfSample 
{        

    #region Manifest 
    [DataContract] 
    public class Manifest 
    { 
        private List<Box> _boxes = new List<Box>(); 
        private List<Book> _books = new List<Book>(); 
        private List<LineItem> _lineItems = new List<LineItem>();        

        [DataMember] 
        public List<Box> Boxes 
        { 
            get 
            { 
                return _boxes; 
            } 
            private set 
            { 
                _boxes = value; 
            } 
        }        

        [DataMember] 
        public List<Book> Books 
        { 
            get 
            { 
                return _books; 
            } 
            private set 
            { 
                _books = value; 
            } 
        }        

        [DataMember] 
        public List<LineItem> LineItems 
        { 
            get 
            { 
                return _lineItems; 
            } 
            private set 
            { 
                _lineItems = value; 
            } 
        }        

        public void AddLineItem(Book book, Box box) 
        { 
            Books.Add(book); 
            Boxes.Add(box); 
            LineItems.Add(new LineItem(book, box)); 
        }        

        public void Serialize(string fileName) 
        { 
            //DataContractSerializer ds = new DataContractSerializer(this.GetType()); 
            DataContractSerializer ds = new DataContractSerializer(this.GetType(), null, 100, false, true, null);        

            using (Stream stream = File.Create(fileName)) 
            { 
                ds.WriteObject(stream, this); 
            } 
        }        

        public void Deserialize(string fileName) 
        { 
            using (Stream stream = File.OpenRead(fileName)) 
            { 
                DataContractSerializer ds = new DataContractSerializer(this.GetType()); 
                Manifest manifest = (Manifest)ds.ReadObject(stream); 
                this.Books = manifest.Books; 
                this.Boxes = manifest.Boxes; 
                this.LineItems = manifest.LineItems; 
            } 
        }        

        public override string ToString() 
        { 
            StringBuilder sb = new StringBuilder();        

            foreach (LineItem li in this.LineItems) 
            { 
                sb.AppendLine(String.Format("Box {0}\t{1}", li.Box.Id, li.Book.Title)); 
            }        

            return sb.ToString(); 
        } 
    #endregion        

        #region LineItemClass 
        [DataContract] 
        public class LineItem 
        { 
            private Book _book; 
            private Box _box;        

            public LineItem(Book book, Box box) 
            { 
                _box = box; 
                _book = book; 
            }        

            [DataMember] 
            public Box Box 
            { 
                get 
                { 
                    return _box; 
                } 
                private set 
                { 
                    _box = value; 
                } 
            }        

            [DataMember] 
            public Book Book 
            { 
                get 
                { 
                    return _book; 
                } 
                private set 
                { 
                    _book = value; 
                } 
            } 
        } 
        #endregion 
    }         

    #region Book Class        

    [DataContract] 
    public class Book 
    { 
        [DataMember] 
        public string Title { get; set; }        

        [DataMember] 
        public string Author { get; set; } 
    } 
    #endregion        

    #region Box Class 
    [DataContract] 
    public class Box 
    { 
        [DataMember] 
        public int Id { get; set; }        

    } 
    #endregion 
}        

January 1, 2008

Persisting the Doubly Linked List

Filed under: Misc Thoughts — dotnetthoughts @ 9:54 am
Tags: , , ,

Recently, we discovered that I needed a doubly linked list to chain objects together in our code. .Net has made this an incredibly easy process, as it provides a LinkedList generic object which manages the creation of the list, as well as the inserting and the deletion of the nodes.

Our project also requires that we persist our linked list to a database. The task seems easy enough. All that needed to be done was to create a table which contains our data, as well as pointers to the previous and next nodes. In other words, our initial table structure looked like this:

Id            int 
ParentId      int (FK to Id) 
ChildId       int (FK to Id) 
Description   nvarchar(30)

This structure seems to work on the surface, but we very quickly realized two very critical problems.

The first problem is that inserting the data into this data structure requires two passes. On the first pass, we insert all of the records into the database. Only after all of the records have been inserted can we assign links to both the parent and child records in the ParentId and ChildId columns.

foreach (link in theChain) 
   {  //Insert the record. }  

foreach (link in theChain) 
   {  //Update the record to include the parent and child pointers }

 The second problem is that the data can fall out of sync with itself. For example, what happens if the data ends up looking like this due to some misbehaving code? Id 1 believes that the child record should be Id 2, but Id 2 believes that it is the top of its own chain.

Id: 1   ParentId: null   ChildId: 2 
Id: 2   ParentId: null   ChildId: 3

Both of these problems can be solved by treating the doubly linked list as a singly linked list in the database. If you have the links of a chain going in one direction, you should be able to determine the links going the other way. We initally avoided this option, because we thought the query to retrieve the data would be extremely complex. (Query the parent with a union of the child, maybe into a temporary table. Ugh.)

While on a walk, yesterday, though, I came up with the idea of simply writing a query with an additional join that would return the data with the links in both directions. Our database would no longer need the ChildId column. If we order our data so that parents always fall above their children (the natural state of a linked list), we can insert all of this data in a single pass. Since there is no ChildId, the data can’t become inconsistent.

Id            int 
ParentId      int (FK to Id) 
Description   nvarchar(30)

We retrieving data to recreate the LinkedList in code, we can get both parent and child ids by linking the LinkedList table to itself.

SELECT parentList.Id, parentList.ParentId, childList.Id AS ChildId
FROM LinkedList parentList
LEFT JOIN LinkedList childList ON parentList.Id = childList.ParentId

It’s always a neat experience when an elegant solution comes out of the blue to solve a complex problem. I’m amazed at how often walking away and letting the subconscience mind work will lead to a better solution than when it is being actively developed. Seems like a good New Year’s resolution will be to walk more. Leads to a healthier me, and healthier code.

Good luck and code safe!

MW

Blog at WordPress.com.