Search code examples
c#mongodbid-generation

MongoDb sequential int id generator for C# class hierarchy


I have a C# classes hierarchy with separated BsonClass mappings. All my classes use custom sequential int id generation. Last Id stored in special collection called "Counter". My problem is when mongo collection methods like InsertOneAsync get called InvalidCastException is thrown.

Base Entity class:

[Serializable]
public abstract class Entity<TIdentifier> : IEntity<TIdentifier>
{
    public TIdentifier Id { get; set; }

    public DateTime CreatedDate { get; set; }

    public DateTime UpdatedDate { get; set; }

    public virtual bool IsTransient()
    {
        return (object) this.Id == null || this.Id.Equals((object) default (TIdentifier));
    }
}

Child class:

public class Child : Entity<int> 
{
    public int SomeData { get; set; }
}

Base class map:

public class EntityMap
{
    public static void Register()
    {
        BsonClassMap.RegisterClassMap<Entity<int>>(cm =>
        {
            cm.AutoMap();
            cm.MapIdProperty(c => c.Id)
                .SetIdGenerator(SeqIntIdGenerator<Entity<int>>.Instance)
                .SetSerializer(new Int32Serializer(BsonType.Int32));
        });
    }
}

Child class map:

public class ChildMap
{
    public static void Register()
    {
        BsonClassMap.RegisterClassMap<Child>(cm =>
        {
            cm.AutoMap();
            cm.SetIgnoreExtraElements(true);
        });
    }
}

Sequential int id generator class:

public class SeqIntIdGenerator<TEntity> : IIdGenerator
{
    public static SeqIntIdGenerator<TEntity> Instance { get; } = new SeqIntIdGenerator<TEntity>();

    public object GenerateId(object container, object document)
    {
        //InvalidCastException thrown here on InsertOneAsync!!!
        var idSequenceCollection = ((IMongoCollection<TEntity>)container).Database.GetCollection<dynamic>("Counters");

        var filter = Builders<dynamic>.Filter.Eq("_id", ((IMongoCollection<TEntity>)container).CollectionNamespace.CollectionName);
        var update = Builders<dynamic>.Update.Inc("Seq", 1);

        var options = new FindOneAndUpdateOptions<dynamic>
        {
            IsUpsert = true,
            ReturnDocument = ReturnDocument.After
        };

        return idSequenceCollection.FindOneAndUpdate(filter, update, options).Seq;
    }

    public bool IsEmpty(object id)
    {
        return (int)id == 0;
    }
}

InvalidCastException is thrown when casting container from Object to IMongoCollection>. How to do it right? My C# driver version is 2.7.2.


Solution

  • If you are already using dynamic, you can switch out the reflection code for that>

    public object GenerateId(object container, object document)
    {
        var containerDynamic = (dynamic) container;
        var idSequenceCollection = containerDynamic.Database.GetCollection<dynamic>("Counters");
    
        var filter = Builders<dynamic>.Filter.Eq("_id", containerDynamic.CollectionNamespace.CollectionName);
        var update = Builders<dynamic>.Update.Inc("Seq", 1);
    
        var options = new FindOneAndUpdateOptions<dynamic>
        {
            IsUpsert = true,
            ReturnDocument = ReturnDocument.After
        };
    
        return idSequenceCollection.FindOneAndUpdate(filter, update, options).Seq;
    }