Sunday, August 21, 2016

.NET async repository using MongoDb

Intro

In this article we will learn how to create async repository for MongoDB as datasource. More info about MongoDb you can find using following link https://www.mongodb.com/. All code from this article you can find using link https://github.com/aliakseimaniuk/MongoRepository.

Repository interface

Instead of classic CRUD methods I prefer to use next repository interface. 

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using MongoRepository.Domain.Entities;

namespace MongoRepository.Domain.Repositories
{
    public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
    {
        Task<TEntity> GetByIdAsync(TKey id);

        Task<TEntity> SaveAsync(TEntity entity);

        Task DeleteAsync(TKey id);

        Task<ICollection<TEntity>> FindAllAsync(Expression<Func<TEntity, bool>> predicate);
    }
}
To be sure that entity is the good fit for the repository we introduced the IEntity interface. Repository entity should implement the specified interface.
namespace MongoRepository.Domain.Entities
{
    public interface IEntity<TKey>
    {
        TKey Id { get; set; }
    }

    public interface IEntity : IEntity<string>
    {
    }
}

Base repository implementation

As we created generic repository interface, we expect that all the repositories will work in the same way. Please find below the implementation of base repository.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoRepository.Domain.Entities;
using MongoRepository.Domain.Repositories;

namespace MongoRepository.Persistence.Repositories
{
    public abstract class BaseMongoRepository<TEntity>
        : IRepository<TEntity, string> where TEntity : IEntity
    {
        protected abstract IMongoCollection<TEntity> Collection { get; }

        public virtual async Task<TEntity> GetByIdAsync(string id)
        {
            return await Collection.Find(x => x.Id.Equals(id)).FirstOrDefaultAsync();
        }

        public virtual async Task<TEntity> SaveAsync(TEntity entity)
        {
            if (string.IsNullOrWhiteSpace(entity.Id))
            {
                entity.Id = ObjectId.GenerateNewId().ToString();
            }

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

            return entity;
        }

        public virtual async Task DeleteAsync(string id)
        {
            await Collection.DeleteOneAsync(x => x.Id.Equals(id));
        }

        public virtual async Task<ICollection<TEntity>> FindAllAsync(
            Expression<Func<TEntity, bool>> predicate)
        {
            return await Collection.Find(predicate).ToListAsync();
        }
    }
}
You might notice, that BaseMongoRepository class has abstract property Collection. This property is representing the data collection of the entities and should be implemented in the derived classes.

Custom entity repository

Previously I mentioned that all derived from BaseMongoRepository class classes  should have Collection property. To get that I created MongoDataContext class.

using System.Configuration;
using MongoDB.Driver;

namespace MongoRepository.Persistence
{
    public class MongoDataContext
    {
        public MongoDataContext()
            : this("MongoDb")
        {
        }

        public MongoDataContext(string connectionName)
        {
            var url = 
                ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;

            var mongoUrl = new MongoUrl(url);
            IMongoClient client = new MongoClient(mongoUrl);
            MongoDatabase = client.GetDatabase(mongoUrl.DatabaseName);
        }

        public IMongoDatabase MongoDatabase { get; }
    }
}

Finally we are ready to implement our custom repository class. I will create PersonRepository as example.

using MongoDB.Driver;
using MongoRepository.Domain.Entities;

namespace MongoRepository.Persistence.Repositories
{
    public class PersonRepository : BaseMongoRepository<Person>
    {
        private const string PeopleCollectionName = "People";

        private readonly MongoDataContext _dataContext;

        public PersonRepository(MongoDataContext dataContext)
        {
            _dataContext = dataContext;
        }

        protected override IMongoCollection<Person> Collection =>
            _dataContext.MongoDatabase.GetCollection<Person>(PeopleCollectionName);
    }
}

namespace MongoRepository.Domain.Entities
{
    public class Person : IEntity
    {
        public string Id { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }
    }
}

MongoDb settings


MongoDb version: 3.2.5
C# MongoDb driver version: 2.2.4

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="MongoDB.Bson" version="2.2.4" targetFramework="net452" />
  <package id="MongoDB.Driver" version="2.2.4" targetFramework="net452" />
  <package id="MongoDB.Driver.Core" version="2.2.4" targetFramework="net452" />
</packages>

1 comment:

  1. What is the use of other method in MongoRepository Class file. You are using just abstract varaible collection

    ReplyDelete