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.
If you like or are using this project to learn or start your solution, please give it a star. Thanks!
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.
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
If you want to help with the project, feel free to open pull requests and submit issues.
If you would like to show your support for this project, then please feel free to buy me a coffee.