1

I got this in my class:

namespace MSAToolsLibrary.PublisherEntry
{
    [XmlRoot(ElementName = "PublisherDatabase", Namespace = "http://www.publictalksoftware.co.uk/msa")]
    public class PublisherData
    {
        public PublisherData()
        {
            //_Publishers = new List<Publisher>();
            _PublishersDictionary = new Dictionary<string, Publisher>();
        }

        public List<Publisher> Publishers
        {
            get { return _PublishersDictionary.Select(x => x.Value).ToList(); }
            set { _PublishersDictionary = value.ToDictionary(x => x.Name, x => x); }
        }
        private Dictionary<string, Publisher> _PublishersDictionary;

        [XmlIgnore]
        public Dictionary<string, Publisher> PublisherDictionary
        {
            get { return _PublishersDictionary; }
        }


        public void AddPublisher(String strName, String strNotes, Gender eGender, Appointed eAppointedAs, Serving eServingAs, bool bUseForDemonstrations, bool bAvailableMidweek, bool bAvailableWeekend, DateTime[] listDatesNotAvailable)
        {
            Publisher _Publisher = new Publisher()
            {
                Name = strName,
                Notes = strNotes,
                Gender = eGender,
                AppointedAs = eAppointedAs,
                ServingAs = eServingAs,
            };

            _Publisher.Assignments.UseForDemonstrations = bUseForDemonstrations;
            _Publisher.Availability.Midweek = bAvailableMidweek;
            _Publisher.Availability.Weekend = bAvailableWeekend;
            _Publisher.Availability.DatesNotAvailable = new List<DateTime>(listDatesNotAvailable);

            //_Publishers.Add(_Publisher);
            _PublishersDictionary.Add(strName, _Publisher);
        }
    }
}

Now, when I save my data to XML it all works good.

But when I read in:

public void ReadPublisherData(out Int64 iResult)
{
    _PublisherData.Publishers.Clear(); // Reset

    iResult = MakeResult(true);

    try
    {
        XmlSerializer x = new XmlSerializer(_PublisherData.GetType());
        using (StreamReader reader = new StreamReader(_strPathXML))
        {
            _PublisherData = (PublisherData)x.Deserialize(reader);
            iResult = _PublisherData.PublisherDictionary.Count;
        }
    }
    catch
    {
        iResult = MakeResult(false);
    }
 }

Doesn't work. I have zero publishers in the list or the dictionary.

What am I doing wrong?

Update

If I change the PublisherData declaration so that it has the needed back field:

public PublisherData()
{
    _Publishers = new List<Publisher>();
    _PublishersDictionary = new Dictionary<string, Publisher>();
}

public List<Publisher> Publishers
{
    get => _Publishers; set => _Publishers = value;
}
private List<Publisher> _Publishers;

Then this causes the data to serialize correctly and I get what is expected in the MFC application. But now my PublisherDictionary is hanging. So I added a function:

   public void BuildPublisherDictionary()
    {
        _PublishersDictionary = _Publishers.ToDictionary(x => x.Name, x => x);
    }

And adjusted the read routine:

   public void ReadPublisherData(out Int64 iResult)
    {
        iResult = MakeResult(true);

        try
        {
            _PublisherData.Publishers.Clear(); // Reset

            XmlSerializer x = new XmlSerializer(_PublisherData.GetType());
            using (StreamReader reader = new StreamReader(_strPathXML))
            {
                _PublisherData = (PublisherData)x.Deserialize(reader);

                _PublisherData.BuildPublisherDictionary();
                iResult = _PublisherData.PublisherDictionary.Count;
            }
        }
        catch
        {
            iResult = MakeResult(false);
        }
     }

It works. But I don't know if that is the simplest way. My problem with the above is that the list / dictionary are now detached from each other. So if I add a publisher to the dictionary it will now not be in the list.

Update 2

At the moment, after reading in the XML, if I add or remove a Publisher I am applying it to both the list and the dictionary. Unless there is a simpler way.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • Start by checking XML file with Notepad to see if text look good. It looks like the serializer is adding a namespace but not the reader. – jdweng Jan 05 '17 at 10:28
  • 1
    Your Publishers property depends on the Dictionary but that Dictionary isn't going to be populated on deserialization. Why can't Publishers be simple implemented with a backing field? – rene Jan 05 '17 at 10:31
  • The XML file is fine because it was created with the application. So I know it's structure is alright. – Andrew Truckle Jan 05 '17 at 10:31
  • @rene I don't quite follow. How should it work then? I need the "Publishers" list for serialization but I need the "PublishersDictionary" for general usage. How should I change it? – Andrew Truckle Jan 05 '17 at 10:33
  • @rene The way I understand it, the Publishers setter is going to fill the dictionary from the value object. – Andrew Truckle Jan 05 '17 at 10:34
  • Rather than `List Publishers` use an array: `Publisher [] Publishers`. See [C# Can't get my dictionary to serialize in XML](http://stackoverflow.com/a/41471786/3744182) for an explanation why this is required. – dbc Jan 10 '17 at 05:23
  • @dbc Thanks. Serialising a List works no problems. And a couple of lines of code converts between that and a dictionary. So I will stick with that. But thanks again. – Andrew Truckle Jan 10 '17 at 05:36

1 Answers1

1

The serializer will only call the getter of your List and then call Add for each element it finds, providing it the instance of the type it deserialized from that element.

If your requirement is to have both a list and a dictionary you'll have to provide an implementation of an type that does so and implements an applicable interface.

The following ListDict uses a List and a Dictionary as their backing stores while implementing IList to be used in the (de)serializtion.

public class ListDict<K, T>:IList<T>
{

   public Dictionary<K, T> dict = new Dictionary<K, T>();
   public List<T> list = new List<T>();
   Func<T,K> KeyFunc;

   // takes an Function that returns a key for T
   public ListDict(Func<T,K> keyfunc) 
   {
      KeyFunc = keyfunc;
   } 

   // called by the serializer
   public void Add(T value) 
   {
      Add( KeyFunc(value), value);
   }

   // fill both List and Dictionary
   public void Add(K key, T value) 
   {
      list.Add(value);
      dict.Add( key , value);
   }
   // left out other required methods from IList<T>
}

Now your PublisherData class will change as follows to leverage above class:

[XmlRoot(ElementName = "PublisherDatabase", Namespace = "http://www.publictalksoftware.co.uk/msa")]
public class PublisherData
{
    private ListDict<string, Publisher> _PublishersDictionary;
    public PublisherData()
    {
        // provide the function to generate a key for a Publisher
        _PublishersDictionary = new ListDict<string,Publisher>( (p) => p.Name  );   
    }

    [XmlElement]
    public ListDict<string,Publisher> Publishers
    { 
        get { return _PublishersDictionary; }
    }


    [XmlIgnore]
    public Dictionary<string, Publisher> PublisherDictionary
    {
        get {return _PublishersDictionary.dict; }
    }
}

Using above classes gives me a filled list and Dictionary directly after deserialization. You'll have to make sure of course to keep the backing stores in sync in the ListDict. Maybe you can do without it but that depends on your exact usecase.

rene
  • 41,474
  • 78
  • 114
  • 152
  • Thanks. I tried this but get a compile error: 1>C:\Program Files (x86)\Microsoft Visual Studio\2017rc\Community\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(4470,5): warning : Type library exporter warning processing 'MSAToolsLibrary.PublisherEntry.ListDict`2, MSAToolsLibrary'. Warning: Type library exporter encountered a generic type. Generic classes may not be exposed to COM. – Andrew Truckle Jan 05 '17 at 16:40
  • You're using that type in a scenario with COM? Or are you compiling for that *just in case*. – rene Jan 05 '17 at 17:00
  • I am building a C# DLL which I am using from MFC. – Andrew Truckle Jan 05 '17 at 17:07
  • Aaaww, Ok. You might better ask a new question then and tag it with MFC and COM so people like Hans Passant might stumble into it. The Marshalling between those two worlds is beyond my current knowledge. I would have to dig deep into that to understand what is going on. – rene Jan 05 '17 at 17:10
  • Do you have much of these combined List/Dictionaries? Because if there is just a few then you can simply build the concrete class without generics. I introduced the generics because your example code already used that. – rene Jan 05 '17 at 17:13
  • I am fairly new to all this. By doing the modifications as I mentioned in the end of the question it works for me. I just have to add / remove / edit from two lists. – Andrew Truckle Jan 05 '17 at 17:18
  • In the end .. if that works ... I have written sloppier code than that .. ;) – rene Jan 05 '17 at 17:22
  • I came up with simple solution. After reading XML file I build the dictionary. The I just use the dictionary. Then, before saving to XML I rebuild the list. Job done. – Andrew Truckle Jan 05 '17 at 17:41