4

I am currently developing a document store in Mongo DB which contains a complete material breakdown of a specific item. The breakdown is calculated and contains a composite structure.

The domain model:

public interface IReagent
{
    int ItemId { get; set; }
    int Quantity { get; set; }
    ConcurrentBag<IReagent> Reagents { get; set; }
}

public class Craft : IReagent
{
    public int ItemId { get; set; }
    public int Quantity { get; set; }
    public int SpellId { get; set; }
    public int Skill { get; set; }
    public Profession Profession { get; set; }
    public ConcurrentBag<IReagent> Reagents { get; set; }
}

public class Reagent : IReagent
{
    public int ItemId { get; set; }
    public int Quantity { get; set; }
    public ConcurrentBag<IReagent> Reagents { get; set; }
}

Now the problem is that a composite structure is not correctly stored. Reagents stays null in mongodb.

/* 28 */
{
  "_id" : ObjectId("4e497efa97e8b617f0d229d4"),
  "ItemId" : 52186,
  "Quantity" : 0,
  "SpellId" : 0,
  "Skill" : 475,
  "Profession" : 8,
  "Reagents" : { }
}

Example of how it should look

{
    "_id" : ObjectId("4e497efa97e8b617f0d229d4"),
    "ItemId" : 52186,
    "Quantity" : 0,
    "SpellId" : 0,
    "Skill" : 475,
    "Profession" : 8,
    "Reagents" : [
        {
            "ItemId" : 521833,
            "Quantity" : 3,
            "SpellId" : 0,
            "Skill" : 400,
            "Profession" : 7,
            "Reagents" : [
                {
                    "ItemId" : 52186,
                    "Quantity" : 3,
                    "SpellId" : 0,
                    "Skill" : 475,
                    "Profession" : 8,
                    "Reagents" : [
                        {
                            "ItemId" : 52183,
                            "Quantity" : 2,
                            "Reagents" : []
                        },
                        {
                            "ItemId" : 521832,
                            "Quantity" : 1,
                            "Reagents" : []
                        }
                    ]
                },
                {
                    "ItemId" : 52386,
                    "Quantity" : 2
                    "SpellId" : 0,
                    "Skill" : 400,
                    "Profession" : 8,
                    "Reagents" : [
                        {
                            "ItemId" : 52383,
                            "Quantity" : 2,
                            "Reagents" : []
                        },
                        {
                            "ItemId" : 523832,
                            "Quantity" : 1,
                            "Reagents" : []
                        }
                    ]
                }
            ]
        }
    ]
}

What could be the problem?

6
  • I assume it is non-null when passed in? Can I check - do you really need ConcurrentBag<T> here? would List<T> not suffice? you might find it happier w/ that? Note that I also wonder if IReagant being an interface is the fundamental problem, as unless it stores type info (i.e. the concrete reagant type) it can't know what to reconstruct Commented Aug 18, 2011 at 9:37
  • When I check "crafts" in runtime debugging it contains a composite structure. I am using the Parallel .NET 4.0 lib of MS to compute the object graph so a ConcurrentBag is needed to avoid using locks. I was already suspecting something like this. That it wouldn't support interfaces, though Craft is also a implementing IReagent and gets stored. Commented Aug 18, 2011 at 9:45
  • Storing an instance of Craft is different to storing an instance of IReagant that happens to be a Craft. Especially to serialization libraries (trust me on this ;p) Commented Aug 18, 2011 at 9:51
  • I believe you :D Just was wondering why the serializer recognizes Craft and not a IEnumerable<IReagent>. It is basically a list mixed with a concrete instances of either Craft or Reagent. The BSON serializer is not that intelligent I guess. I also did some tests with Redis (and the redis driver, in that case it just worked). I guess adding a custom serializer (as posted below) would solve the issue :) Commented Aug 18, 2011 at 9:58
  • because in the case of Craft it knows the type - it is Craft. Now: what object do you create for an IReagant? Commented Aug 18, 2011 at 10:03

2 Answers 2

2

The problem is you are using list of abstractions and it cannot serialize these to JSON, so basically you would need to write your own custom serialization. Here is example of custom serialization I wrote:

 public class FieldsWrapper : IBsonSerializable
    {
        public List<DataFieldValue> DataFieldValues { get; set; }


        public object Deserialize(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
        {
        if (nominalType != typeof(FieldsWrapper)) throw new ArgumentException("Cannot deserialize anything but self");
        var doc = BsonDocument.ReadFrom(bsonReader);
        var list = new List<DataFieldValue>();
        foreach (var name in doc.Names)
        {
            var val = doc[name];
            if (val.IsString)
                list.Add(new DataFieldValue {LocalIdentifier = name, Values = new List<string> {val.AsString}});
            else if (val.IsBsonArray)
            {
                DataFieldValue df = new DataFieldValue {LocalIdentifier = name};
                foreach (var elem in val.AsBsonArray)
                {
                    df.Values.Add(elem.AsString);
                }
                list.Add(df);
            }
        }
        return new FieldsWrapper {DataFieldValues = list};
        }


        public void Serialize(MongoDB.Bson.IO.BsonWriter bsonWriter, Type nominalType, IBsonSerializationOptions options)
        {
            if (nominalType != typeof (FieldsWrapper))
                throw new ArgumentException("Cannot serialize anything but self");
            bsonWriter.WriteStartDocument();
            foreach (var dataFieldValue in DataFieldValues)
            {

                bsonWriter.WriteName(dataFieldValue.LocalIdentifier);
                if (dataFieldValue.Values.Count != 1)
                {
                    var list = new string[dataFieldValue.Values.Count];
                    for (int i = 0; i < dataFieldValue.Values.Count; i++)
                        list[i] = dataFieldValue.Values[i];
                    BsonSerializer.Serialize(bsonWriter, list); 
                }
                else
                {
                    BsonSerializer.Serialize(bsonWriter, dataFieldValue.Values[0]); 
                }
            }
            bsonWriter.WriteEndDocument();
        }

    }

In your case I would write my custom serialization on the level of Reagent class

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for posting, defenitly gonna try this out tonight :)
0

Implenting IBsonSerializer worked :) Did a quick mock up and it worked pretty well :D I got another suggestion from someone who says a List<T> would work, gonna try that later this week. And post the results here.

/* 3131 */
{
  "_id" : ObjectId("4e4d58df77d2cf00691aaef2"),
  "ItemId" : 28432,
  "Quantity" : 0,
  "Reagents" : [{
      "ItemId" : 23448,
      "Quantity" : 0,
      "Reagents" : [{
          "ItemId" : 23447,
          "Quantity" : 0,
          "Reagents" : [{
              "ItemId" : 23427,
              "Quantity" : 2,
              "Reagents" : []
            }]
        }, {
          "ItemId" : 23445,
          "Quantity" : 0,
          "Reagents" : [{
              "ItemId" : 23424,
              "Quantity" : 2,
              "Reagents" : []
            }]
        }]
    }, {
      "ItemId" : 23572,
      "Quantity" : 8,
      "Reagents" : []
    }, {
      "ItemId" : 28431,
      "Quantity" : 0,
      "Reagents" : [{
          "ItemId" : 23571,
          "Quantity" : 0,
          "Reagents" : [{
              "ItemId" : 21885,
              "Quantity" : 1,
              "Reagents" : []
            }, {
              "ItemId" : 21884,
              "Quantity" : 1,
              "Reagents" : []
            }, {
              "ItemId" : 22451,
              "Quantity" : 0,
              "Reagents" : [{
                  "ItemId" : 21885,
                  "Quantity" : 1,
                  "Reagents" : []
                }]
            }, {
              "ItemId" : 22452,
              "Quantity" : 1,
              "Reagents" : []
            }, {
              "ItemId" : 22457,
              "Quantity" : 0,
              "Reagents" : [{
                  "ItemId" : 21884,
                  "Quantity" : 1,
                  "Reagents" : []
                }]
            }]
        }, {
          "ItemId" : 22456,
          "Quantity" : 0,
          "Reagents" : [{
              "ItemId" : 21885,
              "Quantity" : 1,
              "Reagents" : []
            }]
        }, {
          "ItemId" : 23573,
          "Quantity" : 0,
          "Reagents" : [{
              "ItemId" : 23446,
              "Quantity" : 0,
              "Reagents" : [{
                  "ItemId" : 23425,
                  "Quantity" : 2,
                  "Reagents" : []
                }]
            }]
        }, {
          "ItemId" : 23448,
          "Quantity" : 0,
          "Reagents" : [{
              "ItemId" : 23447,
              "Quantity" : 0,
              "Reagents" : [{
                  "ItemId" : 23427,
                  "Quantity" : 2,
                  "Reagents" : []
                }]
            }, {
              "ItemId" : 23445,
              "Quantity" : 0,
              "Reagents" : [{
                  "ItemId" : 23424,
                  "Quantity" : 2,
                  "Reagents" : []
                }]
            }]
        }]
    }]
}

Serialization impl:

public void Serialize(MongoDB.Bson.IO.BsonWriter bsonWriter, Type nominalType, MongoDB.Bson.Serialization.IBsonSerializationOptions options)
{
    if (nominalType != typeof(IReagent) && nominalType != typeof(Reagent) && nominalType != typeof(Craft))
    {
        throw new ArgumentException("Cannot serialize anything but self");
    }

    bsonWriter.WriteStartDocument();

    bsonWriter.WriteInt32("ItemId", this.ItemId);
    bsonWriter.WriteInt32("Quantity", this.Quantity);

    if (this.Reagents != null)
    {
        bsonWriter.WriteName("Reagents");
        bsonWriter.WriteStartArray();

        foreach (var r in this.Reagents)
        {
            BsonSerializer.Serialize(bsonWriter, r.GetType(), r, options);
        }

        bsonWriter.WriteEndArray();
    }

    bsonWriter.WriteEndDocument();

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.