1

With this question ASP.NET Core Web API and MongoDB with multiple Collections answers I can see the perfect use of singleton and lazy initialization for mongodb's WITH single database and multiple collection.

Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "Asset Model API", Version = "v1" });
        });

        ConfigureMongoDb(services);

        services.AddControllers()
            .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
    }

    private 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));
        }
    }

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

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

BookService.cs

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

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

    public List<Book> Get() => _books.Find(book => true).ToList();
}

BooksController.cs

public class BooksController : ControllerBase
{
    private readonly BookService _bookService;

    public BooksController(BookService bookService)
    {
        _bookService = bookService;
    }

    [HttpGet]
    public ActionResult<List<Book>> Get() => _bookService.Get();
}

My use case: I have multiple databases and my API end point will accept database name as ONE of the argument

  [HttpGet]
    public ActionResult<List<Book>> Get(string dbName) => _bookService.Get(dbName);

Now question is what changes requires in my service class and ConfigureMongoDb method of startup class so that I can still get lazy initialization and singleton support?

1 Answer 1

2

Instead of injecting the IMongoCollection into the services (BooksService, ...), you could create a class that can provide the collection based upon the database name, e.g.

public class MongoCollectionProvider
{
  private readonly IMongoClient _client;

  public MongoDbCollectionProvider(IMongoClient client) 
  {
    _client = client;
  }

  public IMongoCollection<T> GetCollection<T>(string database, string collection)
  {
    var db = _client.GetDatabase(database);
    return db.GetCollection<T>(collection);
  }
}

You can then inject this class into the services and retrieve the collection before querying the database, e.g.

public class BookService
{
    private readonly MongoCollectionProvider _prov;
    private readonly string _coll;

    public BookService(MongoCollectionProvider prov, string coll)
    {
        _prov = prov;
        _coll = coll;
    }

    public List<Book> Get(string dbName) => _prov.GetCollection<Book>(dbName, _coll).Find(book => true).ToList();
}

You can register the services like this:

private void ConfigureMongoDb(IServiceCollection services)
{
  var settings = GetMongoDbSettings();
  services.AddSingleton(_ => CreateMongoClient(settings));
  services.AddSingleton<MongoCollectionProvider>();

  services.AddSingleton(sp => 
  {
    var prov = sp.GetRequiredService<MongoCollectionProvider>();
    return new AuthorsService(prov, settings.AuthorsCollectionName);
  }
  services.AddSingleton(sp => 
  {
    var prov = sp.GetRequiredService<MongoCollectionProvider>();
    return new BooksService(prov, settings.BooksCollectionName);
  }
}

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

private static IMongoDatabase CreateMongoClient(DatabaseSettings settings)
{
  return new MongoClient(settings.ConnectionString);
}

If you want to constrain the databases to specific ones, you can change the MongoCollectionProvider to only accept registered database names.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much @Markus. Can I write a common method for various AddSingleton for different services AuthorsService, BooksService, etc like example above void AddMongoDbService<TService, TModel>(string collectionName) { services.AddSingleton(sp => sp.GetRequiredService<IMongoDatabase>().GetCollection<TModel>(collectionName)); services.AddSingleton(typeof(TService)); } ?
The problem is the collection name that changes from service to service and in addtion is configurable. If you can change this to use a constant in the service (e.g. BooksService -> collection name "Books", ...) then you do not need the factory method anymore and you can register the services as a singleton like this services.AddSingleton<BooksService>();.
If you need the collection names to be configurable, you could create a dedicated class, e.g. CollectionNameProvider that receives the configured collection names as input during service registration and provides them to the services by properties, e.g. BooksCollectionName. The services need a constructor parameter for the collection name provider and can pick the configured collection name from the provider.
Thanks. Also I need to pass more than one collection for service class return new BookService(dbCollectionProvider, settings.BooksCollectionName, settings.AuthorsCollectionName). It's make sense not to use some common method. Thanks for all your help. :)

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.