2

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.

2 Answers2

1

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;
}
ntohl
  • 2,067
  • 1
  • 28
  • 32
  • Thanks for your answer, but suppose we have many child classes derived from Entity. What your solution will look like? Trying to cast container to every IMongoCollection is not the best solution imho. – Александр Сысоев Jan 30 '19 at 17:15
  • Yes. That is why I wrote "is sufficient". If something comes into my mind to use `TEntity` as base generic type, I'll modify the code – ntohl Jan 30 '19 at 17:23
  • 1
    @АлександрСысоев please check out, if that is nicer code or not to the reflection code. – ntohl Feb 02 '19 at 12:08
0

Ok, here is my ugly solution using reflection

public class SeqIntIdGenerator<TEntity> : IIdGenerator
{
    var containerType = container.GetType();
    var database = (IMongoDatabase)containerType.GetProperty("Database").GetValue(container);
    var collectionNamespace = (CollectionNamespace)containerType.GetProperty("CollectionNamespace").GetValue(container);

    var idSequenceCollection = database.GetCollection<dynamic>("Counters");

    var filter = Builders<dynamic>.Filter.Eq("_id", 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;
    }
}