1

I have WebAPI which interacts with MongoDB.

I created all of my MongoDB collection classes as singleton using the lazy approach. I understood it was a "standard" approach, but since I am using .NET Core for my web API, I found this tutorial that at first glance described how to do it more efficiently for .NET Core apps.

Anyway, it explains how to do it with one database, containing one collection only, which is quite limited.

With for example 2 collections, it seems even deprecated. For example this part of code:

public BookService(IBookstoreDatabaseSettings settings)
{
    var client = new MongoClient(settings.ConnectionString);
    var database = client.GetDatabase(settings.DatabaseName);

    _books = database.GetCollection<Book>(settings.BooksCollectionName);
}

If I have a second collection with the same kind of constructor, it will create a new instance of MongoClient which is not recommended by the MongoDB documentation:

It is recommended to store a MongoClient instance in a global place, either as a static variable or in an IoC container with a singleton lifetime.

My question then is: is there a better way of using MongoDB with multiple collections with .NET Core (kind of join this question), or is it better to use the "universal lazy" way?

1

5 Answers 5

4

I think, Retic's approach is not so bad.

Lets continue on his answer and extract the database configuration into a separate method ConfigureMongoDb:

public void ConfigureServices(IServiceCollection services)
{
    ConfigureMongoDb(services);

    services.AddControllers()
        .AddNewtonsoftJson(options => options.UseMemberCasing());
}

private void ConfigureMongoDb(IServiceCollection services)
{
    var settings = GetMongoDbSettings();
    var db = CreateMongoDatabase(settings);

    var collectionA = db.GetCollection<Author>(settings.AuthorsCollectionName);
    services.AddSingleton(collectionA);
    services.AddSingleton<AuthorService>();

    var collectionB = db.GetCollection<Book>(settings.BooksCollectionName);
    services.AddSingleton(collectionB);
    services.AddSingleton<BookService>();
}

private BookstoreDatabaseSettings GetMongoDbSettings() =>
    Configuration.GetSection(nameof(BookstoreDatabaseSettings)).Get<BookstoreDatabaseSettings>();

private IMongoDatabase CreateMongoDatabase(BookstoreDatabaseSettings settings)
{
    var client = new MongoClient(settings.ConnectionString);
    return client.GetDatabase(settings.DatabaseName);
}

or in a more compact form:

private void ConfigureMongoDb(IServiceCollection services)
{
    var settings = GetMongoDbSettings();
    var db = CreateMongoDatabase(settings);

    AddMongoDbService<AuthorService, Author>(settings.AuthorsCollectionName);
    AddMongoDbService<BookService, Book>(settings.BooksCollectionName);

    void AddMongoDbService<TService, TModel>(string collectionName)
    {
        services.AddSingleton(db.GetCollection<TModel>(collectionName));
        services.AddSingleton(typeof(TService));
    }
}

The downside with this approach is, that all mongodb related instances (except the services) are created at startup. This is not always bad, because in the case of wrong settings or other mistakes you get immediate response on startup.

If you want lazy initialization for this instances, you can register the database creation and the collection retrieval with a factory method:

public void ConfigureMongoDb(IServiceCollection services)
{
    var settings = GetMongoDbSettings();
    services.AddSingleton(_ => CreateMongoDatabase(settings));

    AddMongoDbService<AuthorService, Author>(settings.AuthorsCollectionName);
    AddMongoDbService<BookService, Book>(settings.BooksCollectionName);

    void AddMongoDbService<TService, TModel>(string collectionName)
    {
        services.AddSingleton(sp => sp.GetRequiredService<IMongoDatabase>().GetCollection<TModel>(collectionName));
        services.AddSingleton(typeof(TService));
    }
}

In the service you have to inject just the registered collection.

public class BookService
{
    private readonly IMongoCollection<Book> _books;

    public BookService(IMongoCollection<Book> books)
    {
        _books = books;
    }
}

public class AuthorService
{
    private readonly IMongoCollection<Author> _authors;

    public AuthorService(IMongoCollection<Author> authors)
    {
        _authors = authors;
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Alright, thanks, I think it put me in the right direction. Still why can't I do services.AddSingleton(new AuthorService(collectionA)); instead of services.AddSingleton(collectionA); + services.AddSingleton<AuthorService>(); ? What is the difference between those two?
@loyd.f: Of course you can register the AuthorService like this: services.AddSingleton(new AuthorService(db.GetCollection<Author>(settings.AuthorsCollectionName)));. The difference is in this case the AuthorService instance will be created at startup, otherwise it will be created when the controller is the first time created and only then. The AuthorsController will be only created if a client calls the Uri (https://..../api/authors). If this is never happened, no AuthorsController and no AuthorService will be created if you use services.AddSingleton<AuthorService>()
@Peter, Could you see my extension question here and give me some suggestion. Thanks and Appreciate! stackoverflow.com/questions/71232942/…
2

You can register a single Instance of the IMongoDatabase in your Services container. then you can add Singleton Collections to your services container using the IMongoDatabase Instance.

var client = new MongoClient(connectionString);
var db = client.GetDatabase(dbName);
 
var collectionA = db.GetCollection<Model>(collectionName);
services.AddSingleton<IMongoDatabase, db>();
services.AddSingleton<IMongoCollection, collectionA>();

to use these you would expose your services to your controllers via the controllers constructor.

public class SomeController
{
   private readonly IMongoCollection<SomeModel> _someCollection;

   public SomeController(IMongoCollection<SomeModel> someCollection)
   {
      _someCollection = someCollection;
   }
}

Comments

1

I am working on a project that will have multiple collection. I am not sure about how to structure the appSetting.json file:

"DatabaseSettings": {
"ConnectionString": "somestrings",
"DatabaseName": "somename",
"CollectionName": "One Collection"

}, is it fine to have to do an array of the collection name:

"DatabaseSettings": {
    "ConnectionString": "somestrings",
    "DatabaseName": "somename",
    "CollectionName": ["One Collection", "Second Collection" "Third Collection"]
  }, 

1 Comment

Is this a question or an answer? Because I am unsure what's your motive here
1

I register it as a singleton like this:

builder.Services.Configure<DatabaseSettings>(builder.Configuration.GetSection("DatabaseSettings"));

builder.Services.AddSingleton<IMongoDatabase>(sp => {
    
    var databaseSettings = sp.GetRequiredService<IOptions<DatabaseSettings>>().Value;
    var mongoDbClient = new MongoClient(databaseSettings.ConnectionString);
    var mongoDb = mongoDbClient.GetDatabase(databaseSettings.DatabaseName);

    return mongoDb;
});


builder.Services.AddScoped<IStudentRepository, StudentRepository>();

I get settings from appsettings.json like this:

{
  "DatabaseSettings": {
    "ConnectionString": "mongodb+srv://xwezi:[email protected]",
    "DatabaseName": "school"
  }
}

and I'm using in my repository like this:

public class StudentRepository : IStudentRepository
{
    private readonly DatabaseSettings databaseSettings;
    private readonly IMongoCollection<Student> students;

    public StudentRepository(
        IOptions<DatabaseSettings> databaseOptions,
        IMongoDatabase mongoDatabase)
    {
        databaseSettings = databaseOptions.Value;
        students = mongoDatabase.GetCollection<Student>("students");
    }

    public Student? GetStudent(string id)
    {
        return students?.Find(s => s.Id == id).FirstOrDefault();
    }

    public IList<Student>? GetStudents()
    {
        return students?.Find(s => true).ToList();
    }
}

Comments

0

I return just a mongo client when i register it and get the database later.. i don't see any reason why not to?

builder.Services.AddSingleton<IMongoClient>(options => {
    var settings = builder.Configuration.GetSection("MongoDBSettings").Get<MongoDBSettings>();
    var client = new MongoClient(settings.ConnectionString);
    return client;
});
builder.Services.AddSingleton<IvMyRepository, vMyRepository>();

Then i use like this:

    public vapmRepository(IMongoClient mongoClient)
    {
        IMongoDatabase mongoDatabase = mongoClient.GetDatabase("mdb01");
        _vprocesshealthCollection = mongoDatabase.GetCollection<vMsg>("vhealth");
    }

Now you have a MongoClient and can connect to any database(s) you want freely for any n collections

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.