Skip to content

It's a modern and generic data access structure for .NET and MongoDB. It supports UnitOfWork, Repository and QueryBuilder patterns. It also includes DbContext, IdGenerators and transactions support with replica set.

License

Notifications You must be signed in to change notification settings

ffernandolima/mongo-db-data-access

Repository files navigation

MongoDB.DataAccess

It's a modern and generic data access structure for .NET and MongoDB. It supports UnitOfWork, Repository and QueryBuilder patterns. It also includes DbContext, IdGenerators and transactions support with replica set.

Give a Star! ⭐

If you like or are using this project to learn or start your solution, please give it a star. Thanks!

Status

build-and-publish Workflow Status

Package NuGet
MongoDB.Data.Infrastructure.Abstractions Nuget Nuget
MongoDB.Data.QueryBuilder.Abstractions Nuget Nuget
MongoDB.Data.Repository.Abstractions Nuget Nuget
MongoDB.Data.UnitOfWork.Abstractions Nuget Nuget
------- -------
MongoDB.Data.Generators Nuget Nuget
MongoDB.Data.Infrastructure Nuget Nuget
MongoDB.Data.QueryBuilder Nuget Nuget
MongoDB.Data.Repository Nuget Nuget
MongoDB.Data.UnitOfWork Nuget Nuget

Installation

MongoDB.DataAccess is available on Nuget.

Install-Package MongoDB.Data.Infrastructure.Abstractions -Version 1.8.1
Install-Package MongoDB.Data.QueryBuilder.Abstractions -Version 1.8.1
Install-Package MongoDB.Data.Repository.Abstractions -Version 1.8.1
Install-Package MongoDB.Data.UnitOfWork.Abstractions -Version 1.8.1

Install-Package MongoDB.Data.Generators -Version 1.8.1
Install-Package MongoDB.Data.Infrastructure -Version 1.8.1
Install-Package MongoDB.Data.QueryBuilder -Version 1.8.1
Install-Package MongoDB.Data.Repository -Version 1.8.1
Install-Package MongoDB.Data.UnitOfWork -Version 1.8.1

P.S.: MongoDB.Data.UnitOfWork depends on the other packages, so installing this package is enough.

Usage

The following code demonstrates basic usage of UnitOfWork, Repository and QueryBuilder patterns.

First of all, please register the dependencies into the MS Built-In container:

public class BloggingContext : MongoDbContext
{
    public BloggingContext(IMongoClient client, IMongoDatabase database, IMongoDbContextOptions options)
        : base(client, database, options)
    { }
}

// Register the DbContext
services.AddMongoDbContext<IMongoDbContext, BloggingContext>(
    connectionString: Configuration.GetValue<string>("MongoSettings:Blogging:ConnectionString"),
    databaseName: Configuration.GetValue<string>("MongoSettings:Blogging:DatabaseName"),
    setupFluentConfigurationOptions: options => options.ScanningAssemblies = new[] { typeof(BloggingContext).Assembly });

// Register the UnitOfWork
services.AddMongoDbUnitOfWork<BloggingContext>();

For multiple databases:

public class BloggingContext : MongoDbContext
{
    public BloggingContext(IMongoClient client, IMongoDatabase database, IMongoDbContextOptions options)
        : base(client, database, options)
    { }
}

public class AccountingContext : MongoDbContext
{
    public AccountingContext(IMongoClient client, IMongoDatabase database, IMongoDbContextOptions options)
        : base(client, database, options)
    { }
}

// Register the DbContexts
services.AddMongoDbContext<IMongoDbContext, BloggingContext>(
    connectionString: Configuration.GetValue<string>("MongoSettings:Blogging:ConnectionString"),
    databaseName: Configuration.GetValue<string>("MongoSettings:Blogging:DatabaseName"),
    setupFluentConfigurationOptions: options => options.ScanningAssemblies = new[] { typeof(BloggingContext).Assembly });

services.AddMongoDbContext<IMongoDbContext, AccountingContext>(
    connectionString: Configuration.GetValue<string>("MongoSettings:Accounting:ConnectionString"),
    databaseName: Configuration.GetValue<string>("MongoSettings:Accounting:DatabaseName"),
    setupFluentConfigurationOptions: options => options.ScanningAssemblies = new[] { typeof(AccountingContext).Assembly });

// Register the UnitOfWork
services.AddMongoDbUnitOfWork<BloggingContext>();
services.AddMongoDbUnitOfWork<AccountingContext>();

For multi-tenancy:

P.S: There are many approaches to implementing multi-tenancy in applications (Discriminator, Database per tenant, Schema per tenant...) and this one is just an example.

public class BloggingContext : MongoDbContext
{
    public BloggingContext(IMongoClient client, IMongoDatabase database, IMongoDbContextOptions options)
        : base(client, database, options)
    { }
}

// Register the DbContexts
services.AddMongoDbContext<IMongoDbContext, BloggingContext>(
    connectionString: Configuration.GetValue<string>("MongoSettings:Blogging:TenantA:ConnectionString"),
    databaseName: Configuration.GetValue<string>("MongoSettings:Blogging:TenantA:DatabaseName"),
    setupDbContextOptions: options => options.DbContextId = $"{nameof(BloggingContext)} - TenantA",
    setupFluentConfigurationOptions: options => options.ScanningAssemblies = new[] { typeof(BloggingContext).Assembly });

services.AddMongoDbContext<IMongoDbContext, BloggingContext>(
    connectionString: Configuration.GetValue<string>("MongoSettings:Blogging:TenantB:ConnectionString"),
    databaseName: Configuration.GetValue<string>("MongoSettings:Blogging:TenantB:DatabaseName"),
    setupDbContextOptions: options => options.DbContextId = $"{nameof(BloggingContext)} - TenantB",
    setupFluentConfigurationOptions: options => options.ScanningAssemblies = new[] { typeof(BloggingContext).Assembly });

// Register the UnitOfWork
services.AddMongoDbUnitOfWork<BloggingContext>();

After that, use the structure in your code like that:

private readonly IMongoDbUnitOfWork<BloggingContext> _unitOfWork;

// Injection
public BlogsController(IMongoDbUnitOfWork<BloggingContext> unitOfWork)
    => _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork), $"{nameof(unitOfWork)} cannot be null.");

// Or
// Factory for multi-tenancy
public BlogsController(IMongoDbUnitOfWorkFactory<BloggingContext> unitOfWorkFactory, ITenantProvider tenantProvider) 
{
    if (unitOfWorkFactory is null)
    {
        throw new ArgumentNullException(nameof(unitOfWorkFactory), $"{nameof(unitOfWorkFactory)} cannot be null.");
    }

    if (tenantProvider is null)
    {
        throw new ArgumentNullException(nameof(tenantProvider), $"{nameof(tenantProvider)} cannot be null.");
    }

    var tenantId = tenantProvider.GetTenantId();
    _unitOfWork = unitOfWorkFactory.Create(tenantId);
}

public void GetAllBlogs()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.MultipleResultQuery();

    var blogs = repository.Search(query);
}

public void GetAllBlogsProjection()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.MultipleResultQuery()
                          .Select(selector => new 
                          {
                              Name = selector.Title, 
                              Link = selector.Url, 
                              Type = selector.Type.Description 
                          });

    var blogs = repository.Search(query);
}

public void GetAllOrderedBlogs()
{
    var repository = _unitOfWork.Repository<Blog>();

    IMongoDbQuery<Blog> query;
    IList<Blog> blogs;

    query = repository.MultipleResultQuery()
                      .OrderByDescending("Id");

    blogs = repository.Search(query);

    query = repository.MultipleResultQuery()
                      .OrderByDescending(blog => blog.Id);

    blogs = repository.Search(query);
}

public void GetTopBlogs()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.MultipleResultQuery()
                          .Top(10);

    var blogs = repository.Search(query);
}

public void GetPagedBlogs()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.MultipleResultQuery()
                          .Page(1, 20);

    var blogs = repository.Search(query);
}

public void GetBlogsPagedList()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.MultipleResultQuery()
                          .Page(1, 20);

    var blogs = repository.Search(query)
                          .ToPagedList(
                            query.Paging.PageIndex,
                            query.Paging.PageSize,
                            query.Paging.TotalCount);
}

public void GetFilteredBlogs()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.MultipleResultQuery()
                          .AndFilter(blog => blog.Url.StartsWith("/a/"))
                          .AndFilter(blog => blog.Title.StartsWith("a"))
                          .AndFilter(blog => blog.Posts.Any());

    var blogs = repository.Search(query);
}

public void GetUrls()
{
    var repository = _unitOfWork.CustomRepository<ICustomBlogRepository>();

    var urls = repository.GetAllBlogUrls();
}

public void GetBlogByUrl()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.SingleResultQuery()
                          .AndFilter(blog => blog.Url.StartsWith("/a/"))
                          .OrderByDescending(blog => blog.Id);

    var blogResult = repository.FirstOrDefault(query);
}

public void GetBlogById()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.SingleResultQuery()
                          .AndFilter(blog => blog.Id == 1);

    var blogResult = repository.SingleOrDefault(query);
}

public void GetBlogByIdProjection()
{
    var repository = _unitOfWork.Repository<Blog>();

    var query = repository.SingleResultQuery()
                          .AndFilter(blog => blog.Id == 1)
                          .Select(selector => new 
                          {
                              selector.Id, 
                              Name = selector.Title, 
                              Link = selector.Url, 
                              Type = selector.Type.Description
                          });

    var blogResult = repository.SingleOrDefault(query);
}

public void ExistsBlog()
{
    var repository = _unitOfWork.Repository<Blog>();

    var exists = repository.Any(blog => blog.Url.StartsWith("/a/"));
}

public void GetBlogCount()
{
    var repository = _unitOfWork.Repository<Blog>();

    var count = repository.Count();

    var longCount = repository.LongCount();
}

public void MaxBlogId()
{
    var repository = _unitOfWork.Repository<Blog>();

    var id = repository.Max(blog => blog.Id);
}

public void MinBlogId()
{
    var repository = _unitOfWork.Repository<Blog>();

    var id = repository.Min(blog => blog.Id);
}

public void AddBlog()
{
    var repository = _unitOfWork.Repository<Blog>();

    repository.InsertOne(Seeder.SeedBlog(51));

    _unitOfWork.SaveChanges();
}

public void UpdateBlog()
{
    var repository = _unitOfWork.Repository<Blog>();

    repository.ReplaceOne(x => x.Id == id, model);

    _unitOfWork.SaveChanges();
}

public void DeleteBlog()
{
    var repository = _unitOfWork.Repository<Blog>();

    repository.DeleteOne(x => x.Id == id);

    _unitOfWork.SaveChanges();
}

public void UpdateManyBlogs()
{
    var repository = _unitOfWork.Repository<Blog>();

    repository.UpdateMany(
        blog => blogIds.Contains(blog.Id),
        new Dictionary<Expression<Func<Blog, object>>, object>
        {
            { blog => blog.Title, "updated-title" }
        });
    
    _unitOfWork.SaveChanges();
}

public void BulkWriteBlogs()
{
    var repository = _unitOfWork.Repository<Blog>();
    
    var requests = new List<WriteModel<Blog>>();

    foreach (var blogId in blogIds)
    {
        var filter = Builders<Blog>.Filter.Eq(blog => blog.Id, blogId);
        var definition = Builders<Blog>.Update.Set(blog => blog.Title, $"a{blogId}-updated-title");

        requests.Add(new UpdateOneModel<Blog>(filter, definition));
    }

    repository.BulkWrite(requests);

    _unitOfWork.SaveChanges();
}

The operations above are also available as async.

Please check some available samples here

Support / Contributing

If you want to help with the project, feel free to open pull requests and submit issues.

Donate

If you would like to show your support for this project, then please feel free to buy me a coffee.

Buy Me A Coffee

About

It's a modern and generic data access structure for .NET and MongoDB. It supports UnitOfWork, Repository and QueryBuilder patterns. It also includes DbContext, IdGenerators and transactions support with replica set.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages