Loosly Coupled Database Class – example

I thought I would practice on creating a loosly coupled database model in .NET. Here is in my opinion, a good way to implement mongodb. Nothing else included than the connect part.

  • Create a folder Models
  • Then create a file called Database.cs. Let’s keep it simple and have everything in the same file (even though you should have separate for easier readability)
  • Download the Package: MongoDB.Driver
  • In the file Database.cs, let’s create an interface for database clients, it can by any client therefore a name like IDatabaseClient will suffice.
    public interface IDatabaseClient
    {
        public void Connect(string connectionString);
        public void Disconnect();
    }

This is what all our clients can do which is Connect and Disconnect. Seems reasonble enough.

Then let’s make a client utulizing this interface, in my case I want mongo database going so we will add the functions we need along with the mongo specific client like this.

    internal class MongoDatabaseClient : IDatabaseClient
    {
        public IMongoClient? MongoClient { get; set; } = null;
        public IMongoDatabase? MongoDatabase { get; set; } = null;

        public void Disconnect()
        {
            MongoClient = null;
            MongoDatabase = null;
        }

        public void Connect(string connectionString)
        {
            MongoClient = new MongoClient(connectionString);
            MongoDatabase = MongoClient.GetDatabase(MongoDatabaseConstants.Database);
            // Lets try create a collection
            MongoDatabase.CreateCollection(MongoDatabaseConstants.Collection);
        }
    }

Now we can initalize a MongoDatabaseClient and connect with it if we want. Also, almost forgot, create a file in the Models folder called MongoDatabaseConstants.cs which contains the Database and Collection name.

namespace Tet.Models
{
    public static class MongoDatabaseConstants
    {
        public static readonly string Database = "TestDb";
        public static readonly string Collection = "TestCollection";
    }
}

To make things more clear and easier to use, we can make a kind of wrapper class which takes in any database client in its constructor and thus can connect any database client. The connect method also do a try catch there so you can retrive the error and do whatever you want.

   public class DatabaseClient
    {
        private readonly IDatabaseClient _databaseClient;

        public DatabaseClient(IDatabaseClient databaseClient)
        {
            _databaseClient = databaseClient;
        }

        public void Connect(string connectionString)
        {
            try {
                _databaseClient.Connect(connectionString);
            }
            catch (Exception e) {
                Console.WriteLine(e);
            }
        }
        public void Disconnect()
        {
            try
            {
                _databaseClient.Disconnect();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

Now we have a loosly coupled class structure for our database. Initalize it like this:

MongoDatabaseClient mongo = new MongoDatabaseClient();
DatabaseClient db = new DatabaseClient(mongo);
db.Connect("mongodb://localhost:27017");

If you have any reflections or questions please share!

Write to mongodb

Okay let’s take this into action in a web application. With some small modifications.

  • Create a ClassLibrary to keep better structure
  • Move in your Models folder into the project
  • Create a Repository folder
  • Create a Services folder
  • Create Constans folder and move your Constants file in there.
  • Let’s create a ASP WebApplication with MVC in our solution
  • Then in our database.cs let’s add some more methods which all database clients can have. Let’s add
    • Insert
    • Update
    • Delete
    public interface IDatabaseClient
    {
        public void Connect(string connectionString);
        public void Disconnect();

        public void Insert<T>(T document);
        public void Update<T>(string collectionName, FilterDefinition<T> filter, UpdateDefinition<T> update);
        public void Delete<T>(string collectionName, FilterDefinition<T> filter);
    }

With the new functions, we can now take in any object of any class and utilize this in the other parts of the application. Let’s implement these functions in our MongoDatabaseClient class.

    public class MongoDatabaseClient : IDatabaseClient
    {
        public IMongoClient? MongoClient { get; set; } = null;
        public IMongoDatabase? MongoDatabase { get; set; } = null;

        public void Disconnect()
        {
            MongoClient = null;
            MongoDatabase = null;
        }

        public void Connect(string connectionString)
        {
            MongoClient = new MongoClient(connectionString);
            MongoDatabase = MongoClient.GetDatabase(MongoDatabaseConstants.Database);
        }

        public void Insert<T>(T document)
        {
            var coll = MongoDatabase.GetCollection<T>(MongoDatabaseConstants.Collection);
            coll.InsertOne(document);
        }

        public void Update<T>(string collectionName, FilterDefinition<T> filter, UpdateDefinition<T> update)
        {
            throw new NotImplementedException();
        }

        public void Delete<T>(string collectionName, FilterDefinition<T> filter)
        {
            throw new NotImplementedException();
        }
    }

    public class DatabaseClient
    {
        private readonly IDatabaseClient _databaseClient;

        public DatabaseClient(IDatabaseClient databaseClient)
        {
            _databaseClient = databaseClient;
        }

        public void Connect(string connectionString)
        {
            try {
                _databaseClient.Connect(connectionString);
            }
            catch (Exception e) {
                Console.WriteLine(e);
            }
        }
        public void Disconnect()
        {
            try
            {
                _databaseClient.Disconnect();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

Let’s create a class of our things we want to add to our mongo database, let’s go with chat messages!

  • Create and add ChatMessage.cs to the Models
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace ClassLibrary.Models
{
    public class ChatMessage
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }
        [BsonElement]
        public string Chat { get; set; }
    }
}

As you can see, we use the Bson tags to make it database friendly and ready to be used!

Now, create a ChatRepository.cs

using ClassLibrary.Constants;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tet.Models;

namespace ClassLibrary.Repository
{
    public interface IDatabaseRepostitory
    {
        public void Insert<T>(T document);
    }
    public class ChatRepository : IDatabaseRepostitory
    {
        readonly IDatabaseClient _client;
        public ChatRepository(IDatabaseClient databaseClient)
        {
            _client = databaseClient;
            _client.Connect(MongoDatabaseConstants.ConnectionString);
        }
        public void Insert<T>(T document)
        {
            _client.Insert(document);
        }
    }
}

This is the layer that is communicating directly with our database client. As you can see, as soon as the Repository is initalized, we connect to the client, and we have a Insert function.

Next up, create a ChatService.cs in the Services folder.

using ClassLibrary.Models;
using ClassLibrary.Repository;

namespace ClassLibrary.Services
{
    public interface IChatService
    {
        public void SendMessage<T>(ChatMessage repo);
    }
    public class ChatService : IChatService
    {
        readonly IDatabaseRepostitory _repository;
        public ChatService(IDatabaseRepostitory repo) {
            _repository = repo;
        }

        public void SendMessage<T>(ChatMessage message)
        {
            message.Chat = Guid.NewGuid().ToString();
            _repository.Insert(message);
        }
    }
}

With our service, close to our front-end, we are now using dependency injection to take in our IDatabaseRepostitory.

Next we can now go into any controller and depency inject our ChatService, so for example our HomeController.cs can take use of the Service.

using ClassLibrary.Models;
using ClassLibrary.Services;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using WebApplication2.Models;

namespace WebApplication2.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IChatService _chatService;


        public HomeController(ILogger<HomeController> logger, IChatService chatService)
        {
            _chatService = chatService;
            _logger = logger;
        }

        public IActionResult Index()
        {
            _chatService.SendMessage<ChatMessage>(new ChatMessage());
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Now everytime we go to the HomeController, we will send a message and thus we add a message to the mongo collection! Be sure to alter everything as you want, I didn’t add delete or update, but it’s pretty much the same thing.

Program.cs looks like this with depency injection:

using ClassLibrary.Repository;
using ClassLibrary.Services;
using Tet.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IDatabaseClient, MongoDatabaseClient>();
builder.Services.AddSingleton<IDatabaseRepostitory, ChatRepository>();
builder.Services.AddSingleton<IChatService, ChatService>();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
Project structure

Hope you enjoyed this tutorial. To make everything more loosely coupled we could great a ClientDatabase factory, but that’s for another time!

0 0 votes
Article rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments