views:

392

answers:

3

Hi I have this code:

var store = GetStore();
using (IsolatedStorageFileStream fileStream = store.OpenFile(RootData, FileMode.Create))
{
    DataContractSerializer serializer = new DataContractSerializer(typeof(List<Root>));
    serializer.WriteObject(fileStream, rootdatalist);
}

But this only serializes the rootdatalist, and not the subitems. The rootdatalist has a nodes List property, how do I serialize that, so that I get the list hierarchy serialized?

Since it's dbml generated objects the Nodes property of Root is

public System.Data.Linq.Table<Node> Nodes
{
    get
    {
        return this.GetTable<Node>();
    }
}

My Datacontext return is:

public List<Root> GetRootList(Guid userid)
{
   DataLoadOptions loadopts = new DataLoadOptions();
   loadopts.LoadWith<Root>(s => s.Nodes);
   this.DataContext.LoadOptions = loadopts;
   return this.DataContext.Root.Where(s => s.Nodes.Count(n => n.UserId == userid) > 0).ToList();
}

the Node entityset looks as follows in my dbml designer

[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Root_Node", Storage="_Nodes", ThisKey="Id", OtherKey="RootId")]
[global::System.Runtime.Serialization.DataMemberAttribute(Order=5, EmitDefaultValue=false)]
public EntitySet<Node> Nodes
{
    get
    {
        if ((this.serializing && (this._Nodes.HasLoadedOrAssignedValues == false)))
        {
            return null;
        }
        return this._Nodes;
    }
    set
    {
        this._Nodes.Assign(value);
    }
}

Also I have to have the [Include]tag above my properties or nothing will be loaded. Edit:: To others wanting to serialize dbml classes http://blogs.msdn.com/b/wriju/archive/2007/11/27/linq-to-sql-enabling-dbml-file-for-wcf.aspx

+7  A: 

The DataContractSerializer needs to know about all of the types in your object graph.

Use the constructor overload that lets you specify those as well as the root type:

DataContractSerializer serializer = new DataContractSerializer(typeof(List<Root>), listOfOtherTypes);
Oded
will this also work if the List<Node> is actually EntityRef<Node> in the Root class?
Jakob
@Jakob - It should, so long as you tell the serializer about the type. See the answer from Marc as well, as you do need to specify your `DataContract`s and `DataMember`s correctly too.
Oded
@Oded - since this is generated, the contracts should be correct already. And in *most* scenarios it is **not** necessary to supply an additional data to the ctor. In particular, inheritance can be dealt with via `[KnownType]` etc.
Marc Gravell
+9  A: 

Can you include any more information about the contract types? I would expect, for example, that Root is marked as a data-contract, with the member as a data-member, for example:

[DataContract]
public class Root {
    [DataMember]
    public List<SubItem> Nodes {get;private set;}
}

[DataContract]
public class SubItem {
    [DataMember]
    public int Foo {get;set;}

    [DataMember]
    public string Bar {get;set;}
}

That should then work. If not, it would really help to see your types (or a cut-down version of them, that illustrates the problem).

Marc Gravell
A: 

OK; I've had another go at reproducing this, using the Person / PersonPhone tables (in the Person schema) from a fresh AdventureWorks2008R2 install.

I have the data-context set to "Unidirectional" serialization, with standard LINQ-to-SQL bindings etc (no customisations).

To compare to your scenario, a Person can have PersonPhones, and we are interested in a List<Person>. I've looked at 3 scenarios, each looking at the full set of data:

  1. serializing a vanilla set of Person records
  2. the same as 1, but using LoadWith to get the phone records
  3. the same as 1, but manually iterating the data (note: this may cause N+1 issues)

Here is the results; as you can see, 1 fails in the way you describe, but 2 & 3 work fine, with the distinction that 3 does significantly more TSQL work.

So without further details (ideally a fully reproducible example), it is very hard to investigate further...

Results:

Default
=======
Bytes: 20219898
Original person count: 19972
Original phone count: 0
Deser person count: 19972
Deser phone count: 0
Log: 1140

LoadWith
========
Bytes: 24767996
Original person count: 19972
Original phone count: 19972
Deser person count: 19972
Deser phone count: 19972
Log: 2517

Enumerated
==========
Bytes: 24767996
Original person count: 19972
Original phone count: 19972
Deser person count: 19972
Deser phone count: 19972
Log: 6322697

Test rig:

class Program
{

    static void Main(string[] args)
    {
        using(var dc = new DataClasses1DataContext())
        { // 1: vanilla
            dc.Log = new StringWriter();
            RoundtripAndCount("Default", dc.Persons);
            Console.WriteLine("Log: " + dc.Log.ToString().Length);
        }
        using (var dc = new DataClasses1DataContext())
        { // 2: LoadWith
            dc.Log = new StringWriter();
            var opt = new DataLoadOptions();
            opt.LoadWith<Person>(p => p.PersonPhones);
            dc.LoadOptions = opt;
            RoundtripAndCount("LoadWith", dc.Persons);
            Console.WriteLine("Log: " + dc.Log.ToString().Length);
        }
        using (var dc = new DataClasses1DataContext())
        { // 3: iterate manually
            dc.Log = new StringWriter();
            // manually iterate the data (LINQ-to-Objects)
            GC.KeepAlive(dc.Persons.AsEnumerable().Sum(p=>p.PersonPhones.Count())); // just an opaque method
            RoundtripAndCount("Enumerated", dc.Persons);
            Console.WriteLine("Log: " + dc.Log.ToString().Length);
        }
    }

    static void RoundtripAndCount(string caption, IEnumerable<Person> people)
    {
        Console.WriteLine();
        Console.WriteLine(caption);
        Console.WriteLine(new string('=', caption.Length));
        List<Person> list = people.ToList(), clone;
        using(var ms = new MemoryStream())
        {
            var ser = new DataContractSerializer(typeof (List<Person>));
            ser.WriteObject(ms, list);
            ms.Position = 0;
            clone = (List<Person>) ser.ReadObject(ms);
            Console.WriteLine("Bytes: " + ms.Length);
        }
        Func<Person, int> phoneCount = p => p.PersonPhones.HasLoadedOrAssignedValues ? p.PersonPhones.Count() : 0;
        Console.WriteLine("Original person count: " + people.Count());
        Console.WriteLine("Original phone count: " + people.Sum(phoneCount));

        Console.WriteLine("Deser person count: " + clone.Count());
        Console.WriteLine("Deser phone count: " + clone.Sum(phoneCount));

    }
}

As a side-note, I could tweak protobuf-net to convince L2S into giving it the data (where DataContractSerializer decides to ignore it), but this reproduces scenario 3 with a huge N+1 cost. Consequently, I am not planning to pursue that...

Marc Gravell
@Marc - would you like to have a shared view with ms teamviewer, or something like that?
Jakob
@Marc - are there any [include] tags in the metadata? I have to have those in my project, or nothing loads
Jakob
@Jakob - I'll check. The sample is on my other machine, unfortunately.
Marc Gravell
@Marc - would you be interested in my previous preposal to have an overview of the code via teamviewer?
Jakob
@Marc - can I send you an email with a teamviewer invite?
Jakob
@Jakob - I simply can't at the minute; I'm happy to offer my thoughts, and I've tried to reproduce a few times (see below), but I'm going to be busy at least until the end of the week...
Marc Gravell
@Marc - okay :) I'm perfectly understanding of that, I didn't mean to steal your time, I only thought it would be simpler that way, and I really appreciate your participation in this thread.
Jakob
@Marc - I'm still struggling to see what I'm doing wrong, so if at any point you have 5 minutes in which I can show you my code via teamviewer, please let me know, I'll try to keeep resolving this very trobulesome issue, and if you're not up for it for any reason, it's perfectly Ok too. thanks
Jakob