ASP.NET Core – MongoDB Repository Pattern & Unit Of Work

The implementation of generic repositories and Unit Of Work are complex tasks in MongoDB. The lack of mechanisms in the component hinders its adoption. See how to implement this article.

Unit of Work

Unit Of Work is a standard used to group one or more operations (usually database operations) into a single transaction or “unit of work”, so that all operations are approved or disapproved as one.

Repository Pattern

Repositories are classes or components that encapsulate the logic needed to access the database (repository). Centralize common data access functionality. Providing better maintenance and decoupling of the infrastructure from the domain layer.

Benefits

Code decoupling and re-usability.

Creating a Repository Pattern with Generics in .NET allows you to implement CRUD operations with very little effort and code.

Together with the Unit Of Work, in case of any error in the flow of the operation, it avoids any modification in the bank.

And this control is in the application rather than opening transactions in the bank and avoiding locks in the tables.

Where’s MongoDB?

MongoDB does not have transaction control in the .NET driver. With MongoDB’s popularity increasing, more and more systems are being implemented with it. Soon this article will address how to implement one.

Implementation

For this implementation, you will need the MongoDB.Driver and ServiceStack.Core components.

To install the components, open the Package Manager (View> Other Windows> Package Manager Console) and enter the commands:

Install-Package ServiceStack.Core
Install-Package MongoDB.Driver

Generic Repository

To implement the Repository Pattern, an abstract generics class will be used with CRUD operations.

Interface

public interface IRepository<TEntity> : IDisposable where TEntity : class
{
Task Add(TEntity obj);
Task<TEntity> GetById(Guid id);
Task<IEnumerable<TEntity>> GetAll();
Task Update(TEntity obj);
Task Remove(Guid id);
}

Implementation

public abstract class BaseRepository < TEntity >: IRepository < TEntity > where TEntity : class
{
protected readonly IMongoContext _context ;
protected readonly IMongoCollection < TEntity > DbSet ;

protected BaseRepository ( IMongoContext context )
{
_context = context ;
DbSet = _context . GetCollection < TEntity > ( typeof ( TEntity ). Name );
}

public virtual Task Add ( TEntity obj )
{
return _context . AddCommand ( async () => await DbSet . InsertOneAsync ( obj ));
}

public virtual async Task < TEntity > GetById ( Guid id )
{
var data = await DbSet . FindAsync ( Builders < TEntity >. Filter . Eq ( " _id " , id ));
return data . FirstOrDefault ();
}

public virtual async Task < IEnumerable < TEntity >> GetAll ()
{
var all = await DbSet . FindAsync ( Builders < TEntity >. Filter . Empty );
return all . ToList ();
}

public virtual Task Update ( TEntity obj )
{
return _context . AddCommand ( async () =>
{
await DbSet . ReplaceOneAsync ( Builders < TEntity >. Filter . Eq ( " _id " , obj . GetId ()) obj );
});
}

public virtual Task Remove ( Guid id ) => _context . AddCommand (() => DbSet . DeleteOneAsync ( Builders < TEntity >. Filter . Eq ( " _id " , id )));

public void Dispose ()
{
GC . SuppressFinalize ( this );
}
}

Each Model will have its own implementation. For example a Product class:

public interface IProductRepository : IRepository < Product >
{
}

// Implementation
public class ProductRepository : BaseRepository < Product >, IProductRepository
{
public ProductRepository ( IMongoContext context ): base ( context )
{
}
}

Unit of Work

Unit Of Work will be responsible for performing the transactions that the Repositories have made. For this work to be done, a Mongo Context must be created. This Context will be the connection between the Repository and UoW.

Mongo Context

public class MongoContext : IMongoContext
{
private IMongoDatabase Database { get; set; }
private readonly List<Func<Task>> _commands;
public MongoContext(IConfiguration configuration)
{
// Set Guid to CSharp style (with dash -)
BsonDefaults.GuidRepresentation = GuidRepresentation.CSharpLegacy;

// Every command will be stored and it'll be processed at SaveChanges
_commands = new List<Func<Task>>();

RegisterConventions();

// Configure mongo (You can inject the config, just to simplify)
var mongoClient = new MongoClient(configuration.GetSection("MongoSettings").GetSection("Connection").Value);

Database = mongoClient.GetDatabase(configuration.GetSection("MongoSettings").GetSection("DatabaseName").Value);
}

private void RegisterConventions()
{
var pack = new ConventionPack
{
new IgnoreExtraElementsConvention(true),
new IgnoreIfDefaultConvention(true)
};
ConventionRegistry.Register("My Solution Conventions", pack, t => true);
}

public int SaveChanges()
{
var qtd = _commands.Count;
foreach (var command in _commands)
{
command();
}

_commands.Clear();
return qtd;
}

public IMongoCollection<T> GetCollection<T>(string name)
{
return Database.GetCollection<T>(name);
}

public void Dispose()
{
GC.SuppressFinalize(this);
}

public Task AddCommand(Func<Task> func)
{
_commands.Add(func);
return Task.CompletedTask;
}
}

UoW implementation

public interface IUnitOfWork : IDisposable
{
bool Commit ();
}

public class UnitOfWork : IUnitOfWork
{
private readonly IMongoContext _context ;

public UnitOfWork ( IMongoContext context )
{
_context = context ;
}

public bool Commit ()
{
return _context . SaveChanges () > 0 ;
}

public void Dispose ()
{
_context . Dispose ();
}
}

Configuring Startup.cs

To finalize the configuration, open the project’s Startup.cs and add the DI settings.

public void ConfigureServices ( IServiceCollection services )
{
services . AddMvc (). SetCompatibilityVersion ( CompatibilityVersion . Version_2_2 );
services . AddScoped < IMongoContext , MongoContext > ();
services . AddScoped < IUnitOfWork , UnitOfWork > ();
services . AddScoped < IProductRepository , ProductRepository > ();
}

The project code is available on Bruno Brito’s GitHub

I have then the post by Bruno Brito and translated it in to English, mainly because the article was simple and what I was working on

ILinqRepository at last

LINQ to SQL is a very easy to use and very powerful tool for many things when dealing with SQL.  But the time has come that I need to wrap it all up and use an iRepository pattern.  I’ve Googled for the IRepository and found a few useful references but none that will complete my picture.  How ever I did get some good ideas when looking around othe rimplementation of the IRepository pattern that others had implmented.

So why whould I use the iRepository when Linq to Sql and so good?

There are a few time, and this keeps happening that when you come to save an object you forget to use the SubmitChanges(), and what happens if there is a conflict.  You can handle conflicts in Linq to Sql but you need to add extra code for each time you SubmitChanges().  The iRepository I am going to use will wrap all this up and sort it out for us in to one place.  There are a number of reasons, but mainly the iRepository will provide a layer that I can use to hold all these methods in one place.

First the pattern will use Generics.  So this means that you can passing in any Linq to Sql object and it will perform a task.

So here is the Interface I’ll be using:

public interface IRepository<T> where T : class
    {
        /// <summary>
        /// Return all instances of type T.
        /// </summary>
        /// <returns></returns>
        IEnumerable<T> All();

        /// <summary>
        /// Return all instances of type T that match the expression exp.
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        IEnumerable<T> FindAll(Func<T, bool> exp);

        /// <summary>Returns the single entity matching the expression. Throws an exception if there is not exactly one such entity.</summary>
        /// <param name="exp"></param><returns></returns>
        T Single(Func<T, bool> exp);

        /// <summary>Returns the first element satisfying the condition.</summary>
        /// <param name="exp"></param><returns></returns>
        T First(Func<T, bool> exp);

        /// <summary>
        /// Mark an entity to be deleted when the context is saved.
        /// </summary>
        /// <param name="entity"></param>
        void MarkForDeletion(T entity);

        /// <summary>
        /// Create a new instance of type T.
        /// </summary>
        /// <returns></returns>
        T CreateInstance();

        /// <summary>Persist the data context.</summary>
        void SaveAll();

        long Count(Expression<Func<T, bool>> criteria);

        void Save(T entity);
    }

So that is the easy part, next is the concreate implementation of the interface

public class Repository<T> : IRepository<T>
        where T : class
    {
        protected DataContext DataContext;

        /// <summary>
        /// Return all instances of type T.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<T> All()
        {
            return GetTable;
        }

        /// <summary>
        /// Return all instances of type T that match the expression exp.
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        public IEnumerable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where(exp);
        }

        /// <summary>See _vertexRepository.</summary>
        /// <param name="exp"></param><returns></returns>
        public T Single(Func<T, bool> exp)
        {
            return GetTable.SingleOrDefault(exp);
        }

        /// <summary>See _vertexRepository.</summary>
        /// <param name="exp"></param><returns></returns>
        public T First(Func<T, bool> exp)
        {
            return GetTable.First(exp);
        }

        /// <summary>See _vertexRepository.</summary>
        /// <param name="entity"></param>
        public virtual void MarkForDeletion(T entity)
        {
            DataContext.GetTable<T>().DeleteOnSubmit(entity);
        }

        /// <summary>
        /// Create a new instance of type T.
        /// </summary>
        /// <returns></returns>
        public virtual T CreateInstance()
        {
            var entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        /// <summary>See _vertexRepository.</summary>
        public void SaveAll()
        {
            DataContext.SubmitChanges();
        }

        public long Count(Expression<Func<T, bool>> exp)
        {
            return GetTable.Where(exp).Count();
        }

        public void Save(T entity)
        {
            Save(new List<T> { entity });
            return;
        }

        public void Save(IEnumerable<T> entities)
        {
            var table = DataContext.GetTable<T>();

            foreach (var entity in entities)
            {
                var entity1 = entity;
                var dbEntity = (from p in table
                                where p == entity1
                                select p).FirstOrDefault();

                if (dbEntity == null)
                    table.InsertOnSubmit(entity);

                try
                {
                    //  Save the changes.
                    DataContext.SubmitChanges();
                }

                //  Detect concurrency conflicts.
                catch (ChangeConflictException)
                {
                    //  Resolve conflicts.
                    DataContext.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
                }
            }
        }


        public Repository(DataContext dataContext)
        {
            DataContext = dataContext;
        }

        #region Properties

        private Table<T> GetTable
        {
            get { return DataContext.GetTable<T>(); }
        }

        #endregion
    }

That is it, job done.

You’ll notice that in the Save method it handles quite a lot from inserting a new record, updating an exisiting record if one is found with the addition of an object copier to make life easier and then will save the changes to the database and if there happens to be any conflicts it deals with them.

The Save() will accept either single objects or and IEnumerable of an object making things easier and simpler.

I would love any feed back on what I have produced

To compliment the IRepository concreate class I’ve built a set of tests using MS Test that create and use a test database:

IRepository.zip (4.11 kb)

IRepository.Tests.zip (47.25 kb)