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

DONE – when done means DONE

When is something done?  What does DONE mean?

In the teams of software development, here is a definition that can apply to this process:

Definition of Done

The meaning of done is an agreed upon list of the activities necessary to get product increment to a production ready state.

  • Development completed
  • Passed code and peer review (Pull Request in BitBucket)
  • All Technical documentation is created and stored in the source repository
  • Sanity check to confirm technical debt is identified and logged in the tech debt register identified and logged/recorded?
  • Testing has been completed and no issues logged against acceptance tests.
  • Deployed and tested against the acceptance criteria in a valid environment
  • All non-functional requirements have been met
  • Regression test packs updated
  • Update tickets, including documentation, annotation and outcomes
  • Product owner/Business Designer has signed off the work.
  • Release to Production

These are the necessary steps that apply to the development process where every you work

How to export all tables to csv by one export job

If you just want to export all the tables in a SQL database in to separate CSV files, then this is a quick and easy way of doing it.

  1. Execute below query which generates BCP commands

    SELECT ‘bcp ‘ + st.NAME + ‘ out c:\Target\’ + st.NAME + ‘.csv -c -r -d ‘ + DB_NAME() + ‘ -U user@??????.database.windows.net -S tcp:?????.database.windows.net -P ?????? FROM sys.tables st
  2. Paste the result set into text file. Make batch file and schedule it. (It can also be run in CMD manually)