A Quick MongoDB Repository

You might have seen one of my previous posts on creating a data-access layer for your application in which I gave an example of a very simple in-memory repository to facilitate quick testing during development without having to worry about maintain or defining a schema until your domain has informed the way you’d like to do persistence.

This approach makes use of a generic IRepository interface and while I know this has been debated recently I feel that it enables an easy transition between storage mechanisms and in this case allows a very easy switch to MongoDB as a backing store.

The implementation is as follows:

public class MongoRepository<TEntity, TIdentifier> : IRepository<TEntity, TIdentifier> where TEntity : class, IEntity<TIdentifier>  
    {
        private readonly IMongoDatabase database;

        public MongoRepository(IMongoClient client)
        {
            this.database = client.GetDatabase(ConfigurationManager.AppSettings["MongoDatabase"]);
        }

        public TEntity Get(TIdentifier id)
        {
            return this.database.GetCollection<TEntity>(typeof(TEntity).Name).Find(x => x.Id.Equals(id)).FirstOrDefaultAsync().Result;
        }

        public TEntity Find(ISpecification<TEntity> specification)
        {
            var collection = this.database.GetCollection<TEntity>(typeof(TEntity).Name);

            return collection.Find(specification.Predicate).FirstOrDefaultAsync().Result;
        }

        public IEnumerable<TEntity> GetAll()
        {
            return this.database.GetCollection<TEntity>(typeof(TEntity).Name).Find(new BsonDocument()).ToListAsync().Result;
        }

        public IEnumerable<TEntity> FindAll(ISpecification<TEntity> specification)
        {
            var collection = this.database.GetCollection<TEntity>(typeof(TEntity).Name);

            var listAsync = collection.Find(specification.Predicate).ToListAsync();

            return listAsync.Result;
        }

        public TEntity Save(TEntity entity)
        {
            var collection = this.database.GetCollection<TEntity>(typeof(TEntity).Name);

            collection.ReplaceOneAsync(x => x.Id.Equals(entity.Id), entity, new UpdateOptions
                                                                            {
                                                                                IsUpsert = true
                                                                            });

            return entity;
        }

        public void Delete(TIdentifier id)
        {
            var collection = this.database.GetCollection<TEntity>(typeof(TEntity).Name);

            collection.DeleteOneAsync(x => x.Id.Equals(id));
        }

        public void Delete(TEntity entity)
        {
            var collection = this.database.GetCollection<TEntity>(typeof(TEntity).Name);

            collection.DeleteOneAsync(x => x.Id.Equals(entity.Id));
        }
    }

As you can see, implementing the generic repository interface is quite simple with MongoDB if we use the type name of each aggregate root as a Mongo collection name.

The use of the Specification pattern also allows an easy transition here to another persistence medium since we do not need to update any specific repository implementations to accommodate our new backing store since our query logic is built into our specifications and the C# driver for Mongo can accept expressions for filtering.

The fact that Mongo does not require a schema and the fact that we are using one interface allows us to switch between persisting and not persisting data with a simple configuration switch in our data-access container installer:

if(Convert.ToBoolean(ConfigurationManager.AppSettings["PersistData"] ?? "false"))  
            {
                container.Register(Component.For(typeof(IRepository<,>)).ImplementedBy(typeof(MongoRepository<,>)).LifestyleTransient());    
                MongoMappings.SetupMappings();
            }
            else
            {
                container.Register(Component.For(typeof(IRepository<,>)).ImplementedBy(typeof(MemoryRepository<,>)).LifestyleSingleton());    
            }

We can also specify a connection string using a factory method for the MongoClient which is injected into our repository:

container.Register(  
                Component.For<IMongoClient>().ImplementedBy<MongoClient>().LifestyleSingleton()
                         .UsingFactoryMethod(() => new MongoClient("<ConnectionStringGoesHere>"))
            );

This example shows how easy it can be to switch from an easy, faked persistence mechanism for testing to a real backing store when the layers of your application are properly abstracted.