Consider I have the following operation in a repository:

public T AddCountToTag(T tag, int count) where T: Tag {}

Where the base class for tag is a duple of Name and Count, but there maybe different sub-classes of tag which may consists of more fields. For instance, I try to make a sub class called “Folder” inherited from tag:

[BsonIgnoreExtraElements]
public class Tag{
public int ItemCount {get;set;}
[BsonId]
public string Name {get;set;}
}
[BsonIgnoreExtraElements]
public class Folder : Tag {
public string ParentName {get;set;}
}


Normally, if we dealing with non-generic classes, we can use the atomic operations of update (Note I am using the Official C# Driver):

public Tag AddCountToTag(Tag tag, int count) where Tag {
var collection = db.getCollection<Tag>("Tag");
collection.Update(Query.EQ("_id", tag.Name), Update.Inc("ItemCount", count), UpdateFlags.Upsert);
return collection.FindOne(Query.EQ("_id", tag.Name));
}

In the above repository operation, I need to achieve two things: First, according to the Name given, find the Tag and if the Tag is not already existed, we need to create one; second, add the certain count to the ItemCount field of the given name of the tag, and save it. Since MongoDb does not have explicit transaction control, we have to do the above operation in one database statement. Above code should work perfectly, and since I included the UpdateFlags.Upsert, if the _id is not existed, it will create the entry and put the supplied itemCount as the new entry’s count.

Generic document update

Above would not work for generic version. For example, if we change the signature for the above to:

public T AddCountToTag(T tag, int count) where T: Tag {
var collection = db.getCollection<T>("Tag");
collection.Update(Query.EQ("_id", tag.Name), Update.Inc("ItemCount", count), UpdateFlags.Upsert);
return collection.FindOne(Query.EQ("_id", tag.Name));
}

Because in the update statement, we haven’t included any other fields that is available in sub-classes only (e.g., Folder’s ParentName), it is not going to be saved, even if we supply a Folder as the method argument. In order to fix this behavior, we need an extra class to help writing the Update statement (mainly copied from the UpdateBuilder class[sourcecode on GitHub]):


    [Serializable]
public class GUpdateBuilder : BuilderBase, IMongoUpdate
{
private BsonDocument _document;

public GUpdateBuilder()
{
_document = new BsonDocument();
}

public GUpdateBuilder(object startObject):this()
{
var writer = new BsonDocumentWriter(new BsonDocument(), BsonDocumentWriterSettings.Defaults);
BsonSerializer.Serialize(writer, startObject.GetType(), startObject);
foreach(var element in writer.TopLevelDocument)
{
if (element.Name!="_id")
Set(element.Name, element.Value);
}
}

protected void CheckAndRemoveFromSet(string name) {
var setEle = _document.GetElement("$set");
setEle.Value.AsBsonDocument.Remove(name);
}

        // Most method unchanged

        public GUpdateBuilder Inc(string name, double value)
{
if (name == null) { throw new ArgumentNullException("name"); }
CheckAndRemoveFromSet(name);
Inc(name, BsonValue.Create(value));
return this;
}
    }

The UpdateBuilder is just a builder class helps to write the update command for MongoDb. The only difference I put here is I assume it starts with an object, and defined all the “$set” operation by default. And in case of any other operations requires to update any field (e.g., ItemCount), it will remove the field from “$set” before putting to other oprations. The resulting repository operation is like this:

public T AddCountToTag(T tag, int count) where T: Tag {
var collection = db.getCollection<T>("Tag");
        var gupdate = new GUpdateBuilder(tag);
collection.Update(Query.EQ("_id", tag.Name), gupdate.Inc("ItemCount", count), UpdateFlags.Upsert);
return collection.FindOne(Query.EQ("_id", tag.Name));
}

In this way, our repository can take any subclass and perform atomic operations as usual, with just a minimal change to the code.

Advertisements