MongoDB Repository C examples

In this article, we will learn how to create a repository pattern ie. MongoDB Repository implementation for the No-SQL database in the .NET Core C# application.

We shall be using MongoDB as the NoSQL instance. However, as we know repository design patterns fit into any NoSQL or Relational DB requirements and also can be used for multiple requirements.

We will cover the below aspects in today’s article,

We shall create a MongoDB context object that will resemble EF Core-generated DBContext scaffolding. (ORM framework for relational DB.)

Using ORM tools like EFCore gives us this repository implementation with very minimal effort.

All you need is a generic interface around basic CRUD operations and then you can execute all database operations using Interface and encapsulate all DBContext operations.

We shall be trying to achieve the same with NoSQL i.e MongoDB database in this article.

Here we shall create a DBContext class that will help us to create abstraction around the MongoDB Database object.

mongodb driver c# example, mongodb connection string c#, mongodb c# change tracking,

What is Repository

With the context of today’s article, the Repository pattern lets you create an abstraction layer around the data we are dealing with.

This abstraction exists between the data and the business/domain(DDD) objects of an application.

Application with a complex business/domain model gets a huge advantage from such a layer that not only isolates the business objects from the database access code but also provides a clean separation of concerns.

It allows you to access everything from the database as an object (OOPS) with simplification and ease.

Also, let’s not forget the additional and main advantage of creating a comprehensive testable code due to interface-based implementation.

Repository encourages a more loosely coupled approach to accessing our data from the database.

The code becomes cleaner, maintainable, and highly extensible.

Schema modeling – Create Models using Schema

Do you follow “schema modeling” ??.

If you are about to start developing your models, that means you should be very much ready with the schema or data modeling.

This schema for data will help you define and structure your domain model.

You can consider Naming and quality attributes convention in this phase.

Schema for MongoDB fields

Below is a sample schema,

mongodb c#, mongodb repository pattern nodejs, mongodb c# example project github, mongodb c# connection best practices, mongodb dbcontext c#,

Based on the schema this is how the model class has been defined below,

public class Book
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }

        public string Name { get; set; }

        public decimal Price { get; set; }

        public string Category { get; set; }

        public string Author { get; set; }
    }



Define a Mongo DBContext for the Business Object

We shall now define the MongoDB Context class specific to a business object or the object that needs to be persisted.

This context class will encapsulate the Database Connection and Collection.

This class shall be responsible for establishing a connection to the required database.

After establishing the connection this will hold the DB context in memory or per request basis (based on lifetime management configured in the API pipeline.)

Define Mongo DBContext class

DBContext class will be defined as below,

    
    /// <summary>
    /// 
    /// </summary>
    public class MongoBookDBContext : IMongoBookDBContext
    {
        private IMongoDatabase _db { get; set; }
        private MongoClient _mongoClient { get; set; }
        public IClientSessionHandle Session { get; set; }
        public MongoBookDBContext(IOptions<Mongosettings> configuration)
        {
            _mongoClient = new MongoClient(configuration.Value.Connection);
            _db = _mongoClient.GetDatabase(configuration.Value.DatabaseName);
        }
      
        public IMongoCollection<T> GetCollection<T>(string name)
        {
            return _db.GetCollection<T>(name);
        }
    }

So DBContext class will help to establish an actual connection, providing the database details as required.

Define Mongo DBContext Interface

DBContext Interfaces will be defined as below,

 
public interface IMongoBookDBContext
    {
        IMongoCollection<Book> GetCollection<Book>(string name);
    }

Unit Testing DBContext

Unit testing for the DBContext class is explained here in the below article,


Generic Repository Pattern

Let’s create a generic base repository that will act as an abstraction around basic CRUD operations like Create, Read, Delete, and Update operations.

The main aim of this abstract class is to enforce generic CRUD operations.

Derived classes can have their implementation using the DBContext of their choice.

This generic repository can be extended to serve multiple Business objects if needed.

Base Repository class

Below is a sample Base Repository class. Below are a few sample CRUD operations.

 


public abstract class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class
    {
        protected readonly IMongoBookDBContext _mongoContext;
        protected IMongoCollection<TEntity> _dbCollection;

        protected BaseRepository(IMongoBookDBContext context)
        {
            _mongoContext = context;
            _dbCollection = _mongoContext.GetCollection<TEntity>(typeof(TEntity).Name);
        }
       

Below I have defined two overloaded GET methods where one gets you all the records list and the other GET method gives you results for specified ‘_id’,

        public async Task<TEntity> Get(string id)
        {
            //ex. 5dc1039a1521eaa36835e541

            var objectId = new ObjectId(id);

            FilterDefinition<TEntity> filter = Builders<TEntity>.Filter.Eq("_id", objectId);

            return await _dbCollection.FindAsync(filter).Result.FirstOrDefaultAsync();

        }


        public async Task<IEnumerable<TEntity>> Get()
        {
            var all = await _dbCollection.FindAsync(Builders<TEntity>.Filter.Empty);
            return await all.ToListAsync();
        }

Similarly, You can define the create or update method as provided as an example,

Create or Insert MongoDB example

 public async Task Create(TEntity obj)
        {
            if(obj == null)
            {
                throw new ArgumentNullException(typeof(TEntity).Name + " object is null");
            }
            await _dbCollection.InsertOneAsync(obj);
        }

Update/Replace MongoDB example

The below Example is for the Replace method,

 public virtual void Update(TEntity obj)
        {
            _dbCollection.ReplaceOneAsync(Builders<TEntity>.Filter.Eq("_id", obj.GetId()), obj);
        }

Delete MongoDB example

The below Example is for the delete method,

 
public void Delete(string id)
        {
            //ex. 5dc1039a1521eaa36835e541

            var objectId = new ObjectId(id);
            _dbCollection.DeleteOneAsync(Builders<TEntity>.Filter.Eq("_id", objectId));

        }

Base Repository Interfaces

Base Repository Interfaces will be defined as below,

public interface IBaseRepository<TEntity> where TEntity : class
{       
        Task Create(TEntity obj);
        void Update(TEntity obj);
        void Delete(string id);
        Task<TEntity> Get(string id);
        Task<IEnumerable<TEntity>> Get();
}

Unit Testing Repository

Unit testing for Repository with the Async extension method is explained here in the below article,

After Generic repository implementation, let now define specific to the specific business object we shall be dealing with,

Book Repository Interfaces

Book Repository Interfaces will be defined as below,

public interface IBookRepository : IBaseRepository<Book>
    {
    }

We shall define repository class specific to the context, i.e. here Book DBContext.

This Repository class will mainly focus on the Book database.

BookRepository class will be inherited from the Abstract Base repository and shall use the same generic API.

Book repository has the flexibility to override abstract methods if needed to provide its own implementation.

Book Repository Class

 
public class BookRepository : BaseRepository<Book>, IBookRepository
    {
        public BookRepository(IMongoBookDBContext context) : base(context)
        {
        }
    }

Now that our Repository is ready, this can be integrated into the code.

If following non-DDD or DDD architecture this repository pattern will simply act as a mediator for connecting databases and will be responsible for performing CRUD and also if needed can be used if data persistence is required.

Using a Repository in API/Service

Practically your API will have a Controller method that will reference DDD or Business services interface for performing functionality.

But here for simplicity, I have no Business or Domain layer but I shall be using the repository directly in the controller.

Here is an example of how I am invoking the repository in Controller( Similar way this Repository can be interfaced from the Domain or Business layer.

    
    [Route("api/book")]
    [ApiController]
    public class BookController : ControllerBase
    {
        private readonly IBookRepository _productRepository;

        public BookController(IBookRepository productRepository)
        {
            _productRepository = productRepository;
        }
        
        [HttpGet]
        [Route("catalog")]
        public async Task<ActionResult<IEnumerable<Book>>> Get()
        {
            var products = await _productRepository.Get();
            return Ok(products);
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<Book>> Get(string id)
        {
            var product = await _productRepository.Get(id);
            return Ok(product);
        }
....

...


        }

That’s all! You are set to use a repository in your code.

Other References :

Summary

Today in this article we learned how to implement a Repository pattern around the NoSQL (i.e. MongoDB ) database using MongoDB Driver.

Application with a complex business/domain model gets a huge advantage from the Repository as an abstraction that not only isolates the business objects from the database access code but also provides a clean separation of concerns in an Object-oriented way.

Do you see any improvements to the above code?

Do you have any comments or ideas or any better suggestions to share?

Please sound off your comments below.

Happy Coding !!



Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.



15 thoughts on “MongoDB Repository pattern with example

  1. Very beautifully crafted code ! thank you so much for the tutorial !
    The only thing that might be a bit concerning me is the fact that you are updating your documents using replace instead of update which replaces the whole document instead of updating parts of it so if two users ask for update one of their changes will be lost how would you avoid that?
    Thank you very much !

    1. Hi Seifeddine,
      I think it depends on the “context” of the update. Your example with multiple users is correct.
      But there might be situations where you really want to replace the full document. For example while data-seeding during initialization.

      What’s about implementing an additional method in the repository like

      public virtual void Merge TEntity obj)

      or something like that. Inside, it could use update, instead of replace. Would this help?

  2. Hi
    thanks for this article. It is really helpful.

    I still have a question.
    The class BaseRepository you initialize the member _dbCollection but it is used nowhere.
    All methods that need the the collection gets it using
    _mongoContext.GetCollection(typeof(TEntity).Name);
    .
    My question is, can’t all methods just use the member _dbCollection instead of getting it from _mongoContext?

    1. Hey wk-done, Thank you for your comments. Glad it helped you. Yes, you can same the same reference for all the CRUD operations. I corrected it in the code. Thank you!

  3. public virtual void Update(TEntity obj)
    {
    _dbCollection.ReplaceOneAsync(Builders.Filter.Eq(“_id”, obj.GetId()), obj);
    }

    Where and how you implemented method GetId?
    TEntity is just a class and it hasn’t any method GetId().

  4. Hola
    Muchas gracias por este aporte técnico muy valioso.
    la explicación y dominio del tema fabuloso!, me ayudo a reforzar mis conocimientos técnicos
    en ámbito de desarrollo de software.

    1. Thanks, Deepak for the query. If your question is about adding a new property to the model, then you can add it manually, and accordingly please perform the Create or Update operation. Please visit this . Could you please also specify more details on your question if any?

  5. Great article, thanks a lot!

    Just I was thinking how it could fit my needs, as I need to pass a generic DbContext as parameter to the repositories not only for adding Unit Of Works, but especially because I would like to infer the DnContext instance from a Factory pattern based on a config value, and so the factory can return an instance of MongoDBContext or an instance of the EF DbContext, the problem is that they both have to implement the same interface as a return type of the Factory method.

    Thanks

    1. Hello- Glad, you liked the article. You can extend the above logic for UOW. Coming to your 2nd question – the one you mention would be an ideal implementation that can be achieved to address multiple DB requirements. I had started with the same thought but could finish the pending work.

      I know this answer doesn’t resolve your issue but I shall give it try in the future.

      Please feel free to post your questions if any.

Leave a Reply

Your email address will not be published. Required fields are marked *