All Projects → johnknoop → MongoRepository

johnknoop / MongoRepository

Licence: other
An easy-to-configure, powerful repository for MongoDB with support for multi-tenancy

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to MongoRepository

Ultimate Backend
Multi tenant SaaS starter kit with cqrs graphql microservice architecture, apollo federation, event source and authentication
Stars: ✭ 978 (+2956.25%)
Mutual labels:  multi-tenancy
Multi Tenant
Run multiple websites using the same Laravel installation while keeping tenant specific data separated for fully independent multi-domain setups, previously github.com/hyn/multi-tenant
Stars: ✭ 2,304 (+7100%)
Mutual labels:  multi-tenancy
multi-tenancy-devise
mtdevise adds basecamp style user logins to your ruby on rails application.
Stars: ✭ 27 (-15.62%)
Mutual labels:  multi-tenancy
Hivedscheduler
Kubernetes Scheduler for Deep Learning
Stars: ✭ 126 (+293.75%)
Mutual labels:  multi-tenancy
K8spin Operator
K8Spin multi-tenant operator - OSS
Stars: ✭ 175 (+446.88%)
Mutual labels:  multi-tenancy
Loft
Namespace & Virtual Cluster Manager for Kubernetes - Lightweight Virtual Clusters, Self-Service Provisioning for Engineers and 70% Cost Savings with Sleep Mode
Stars: ✭ 239 (+646.88%)
Mutual labels:  multi-tenancy
Module Zero Forsaken
ASP.NET Boilerplate - ASP.NET Identity Integration Module
Stars: ✭ 511 (+1496.88%)
Mutual labels:  multi-tenancy
multitenant-microservices-demo
Full Isolation in Multi-Tenant SaaS with Kubernetes + Istio
Stars: ✭ 57 (+78.13%)
Mutual labels:  multi-tenancy
Multi Tenancy
Flux v1: Manage a multi-tenant cluster with Flux and Kustomize
Stars: ✭ 180 (+462.5%)
Mutual labels:  multi-tenancy
multitenant
Multi-Tenant Spring Boot Application with separate databases using Hibernate and H2.
Stars: ✭ 15 (-53.12%)
Mutual labels:  multi-tenancy
Tenancy
Automatic multi-tenancy for Laravel. No code changes needed.
Stars: ✭ 2,133 (+6565.63%)
Mutual labels:  multi-tenancy
Devicemanager.api
Web API Framework demonstrates scalable, multitenant, architecture and allows building its own solution in the minutes. Uses: Entity Framework, UnitOfWork, Repository patterns. Wrapped in Docker, Kubernetes
Stars: ✭ 168 (+425%)
Mutual labels:  multi-tenancy
KubeCube
KubeCube is an open source enterprise-level container platform
Stars: ✭ 355 (+1009.38%)
Mutual labels:  multi-tenancy
Aspnetboilerplate
ASP.NET Boilerplate - Web Application Framework
Stars: ✭ 10,061 (+31340.63%)
Mutual labels:  multi-tenancy
awesome-multitenancy
No description or website provided.
Stars: ✭ 26 (-18.75%)
Mutual labels:  multi-tenancy
Kiosk
kiosk 🏢 Multi-Tenancy Extension For Kubernetes - Secure Cluster Sharing & Self-Service Namespace Provisioning
Stars: ✭ 533 (+1565.63%)
Mutual labels:  multi-tenancy
Townhouse
A multi-tenant Laravel app for listing property rentals
Stars: ✭ 218 (+581.25%)
Mutual labels:  multi-tenancy
multi-tenant
Spring boot + Hibernate + Postgresql的多租户实现Demo
Stars: ✭ 110 (+243.75%)
Mutual labels:  multi-tenancy
awesome-landlord
A simple, single database multi-tenancy solution for Laravel 5.2+
Stars: ✭ 41 (+28.13%)
Mutual labels:  multi-tenancy
go-saas
go data framework for saas(multi-tenancy)
Stars: ✭ 101 (+215.63%)
Mutual labels:  multi-tenancy

JohnKnoop.MongoRepository

An easy-to-configure extension to the MongoDB driver, adding support for:

✔️ Multi-tenancy
✔️ Simplified transaction handling, including support for TransactionScope
✔️ Soft-deletes

Install via NuGet

Install-Package JohnKnoop.MongoRepository

Configure mappings, indices, multitenancy etc with a few lines of code:

MongoRepository.Configure()
    .Database("HeadOffice", db => db
        .Map<Employee>()
    )
    .DatabasePerTenant("Zoo", db => db
        .Map<AnimalKeeper>()
        .Map<Enclosure>("Enclosures")
        .Map<Animal>("Animals", x => x
            .WithIdProperty(animal => animal.NonConventionalId)
            .WithIndex(animal => animal.Name, unique: true)
        )
    )
    .Build();

See more options

...then start hacking away

var employeeRepository = mongoClient.GetRepository<Employee>();
var animalRepository = mongoClient.GetRepository<Animal>(tenantKey);

In the real world you'd typically resolve IRepository<T> through your dependency resolution system. See the section about DI frameworks for more info.

Getting started

Querying

Get by id

await repository.GetAsync("id");
await repository.GetAsync<SubType>("id");

// With projection
await repository.GetAsync("id", x => x.TheOnlyPropertyIWant);
await repository.GetAsync<SubType>("id", x => new
    {
        x.SomeProperty,
        x.SomeOtherProperty
    }
);

Find by expression

await repository.Find(x => x.SomeProperty == someValue);
await repository.Find<SubType>(x => x.SomeProperty == someValue);
await repository.Find(x => x.SomeProperty, regexPattern);

Returns an IFindFluent which offers methods like ToListAsync, CountAsync, Project, Skip and Limit

Examples:

var dottedAnimals = await repository
    .Find(x => x.Coat == "dotted")
    .Limit(10)
    .Project(x => x.Species)
    .ToListAsync()

LINQ

repository.Query();
repository.Query<SubType>();

Returns an IMongoQueryable which offers async versions of all the standard LINQ methods.

Examples:

var dottedAnimals = await repository.Query()
    .Where(x => x.Coat == "dotted")
    .Take(10)
    .Select(x => x.Species)
    .ToListAsync()

Inserting, updating and deleting

InsertAsync, InsertManyAsync

await repository.InsertAsync(someObject);
await repository.InsertManyAsync(someCollectionOfObjects);

UpdateOneAsync, UpdateManyAsync

// Update one document
await repository.UpdateOneAsync("id", x => x.Set(y => y.SomeProperty, someValue), upsert: true);
await repository.UpdateOneAsync(x => x.SomeProperty == someValue, x => x.Push(y => y.SomeCollection, someValue));
await repository.UpdateOneAsync<SubType>(x => x.SomeProperty == someValue, x => x.Push(y => y.SomeCollection, someValue));

// Update all documents matched by filter
await repository.UpdateManyAsync(x => x.SomeProperty == someValue, x => x.Inc(y => y.SomeProperty, 5));

UpdateOneBulkAsync

Perform multiple update operations with different filters in one db roundtrip.

await repository.UpdateOneBulkAsync(new List<UpdateOneCommand<MyEntity>> {
    new UpdateOneCommand<MyEntity> {
        Filter = x => x.SomeProperty = "foo",
        Update = x => x.Set(y => y.SomeOtherProperty, 10)
    },
    new UpdateOneCommand<MyEntity> {
        Filter = x => x.SomeProperty = "bar",
        Update = x => x.Set(y => y.SomeOtherProperty, 20)
    }
});

FindOneAndUpdateAsync

This is a really powerful feature of MongoDB, in that it lets you update and retrieve a document atomically.

var entityAfterUpdate = await repository.FindOneAndUpdateAsync(
    filter: x => x.SomeProperty.StartsWith("Hello"),
    update: x => x.AddToSet(y => y.SomeCollection, someItem)
);

var entityAfterUpdate = await repository.FindOneAndUpdateAsync(
    filter: x => x.SomeProperty.StartsWith("Hello"),
    update: x => x.PullFilter(y => y.SomeCollection, y => y.SomeOtherProperty == 5),
    returnProjection: x => new {
        x.SomeCollection
    },
    returnedDocumentState: ReturnedDocumentState.AfterUpdate,
    upsert: true
);

Aggregation

repository.Aggregate();
repository.Aggregate(options);

Returns an IAggregateFluent which offers methods like AppendStage, Group, Match, Unwind, Out, Lookup etc.

Deleting

await repository.DeleteByIdAsync("id");
// or
await repository.DeleteManyAsync(x => x.SomeProperty === someValue);
// or
var deleted = await repository.FindOneAndDeleteAsync("id");
// or
var deleted = await repository.FindOneAndDeleteAsync<DerivedType>(x => x.SomeProp == someValue);

Soft-deleting

Soft-deleting an entity will move it to a different collection, preserving type-information.

await repository.DeleteByIdAsync("id", softDelete: true);
// or
var deleted = await repository.FindOneAndDeleteAsync("id", softDelete: true);

Listing soft-deleted entities:

await repository.ListTrashAsync();

Restoring one (or many) soft-deleten entities

await repository.RestoreSoftDeletedAsync("id");
await repository.RestoreSoftDeletedAsync(x => x.TimestampDeletedUtc > DateTime.Today);

Permanently delete soft-deleted documents

await repository.PermamentlyDeleteSoftDeletedAsync(x => x.Foo == "bar");

Transactions

MongoDB 4 introduced support for multi-document transactions. We provide a simplified interface: you don't have to pass around the session object. Instead we detect any ambient transaction and uses it for all write/update/delete operations.:

using (var transaction = repository.StartTransaction()) {
    // ...
    await transaction.CommitAsync();
}

Since version 5 we also support enlisting with a TransactionScope. This is useful to be able to put a transactional boundary around MongoDB operations and anything that is compatible with TransactionScopes.

using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) {
    repository.EnlistWithCurrentTransactionScope();
    // ...
    transaction.Complete();
}

If you configure the repository with .AutoEnlistWithTransactionScopes() then it will automatically enlist to any ambient TransactionScope without the need to do it explicitly like in the example above.

MongoDB replica sets sometimes encounter transient transaction errors, in which case the recommended course of action from the MongoDB team is to simply retry until it succeeds. We offer a shorthand for this:

// Retry using standard MongoDB transaction
await repo.WithTransactionAsync(async () =>
{
    // your code here
}, maxRetries: 3);

// Retry using TransactionScope
await repo.WithTransactionAsync(async () =>
{
    // your code here
}, TransactionType.TransactionScope, maxRetries: 3);

RetryAsync also comes with an overload that takes a number representing the max number of retries.

UnionWith

This library provides an extension method to IAggregateFluent<T> called UnionWith that accepts a repository and a projection expression.

using JohnKnoop.MongoRepository.Extensions;

var allContacts = await soccerPlayersRepository
    .Aggregate()
    .Project(x => new
    {
        PlayerName = x.SoccerPlayerName,
        TeamName = x.SoccerTeamName
    })
    .UnionWith(
        rugbyPlayersRepository,
        x => new
        {
            PlayerName = x.RugbyPlayerName,
            TeamName = x.RugbyTeamName
        }
    )
    .SortBy(x => x.PlayerName)
    .ToListAsync();

Advanced features

Counters

Auto-incrementing fields is a feature of most relational databases that unfortunately isn't supported by MongoDB. To get around this, counters are a way to solve the problem of incrementing a number with full concurrency support.

var value = await repository.GetCounterValueAsync();
var value = await repository.GetCounterValueAsync("MyNamedCounter");

Atomically increment and read the value of a counter:

var value = await repository.IncrementCounterAsync(); // Increment by 1
var value = await repository.IncrementCounterAsync(name: "MyNamedCounter", incrementBy: 5);

Reset a counter:

await repository.ResetCounterAsync(); // Reset to 1
await repository.ResetCounterAsync(name: "MyNamedCounter", newValue: 5);

Deleting properties

Delete a property from a document:

await repository.DeletePropertyAsync(x => x.SomeProperty == someValue, x => x.PropertyToRemove);

Configuration

Configuration is done once, when the application is started. Use MongoRepository.Configure() as shown below.

Multi-tenancy

Database-per-tenant style multi-tenancy is supported. When defining a database, just use the DatabasePerTenant method:

MongoRepository.Configure()
    // Every tenant should have their own Sales database
    .DatabasePerTenant("Sales", db => db
        .Map<Order>()
        .Map<Customer>("Customers")
    )
    .Build();

The name of the database will be "{tenant key}_{database name}".

Polymorphism

Mapping a type hierarchy to the same collection is easy. Just map the base type using MapAlongWithSubclassesInSameAssembly<MyBaseType>(). It takes all the same arguments as Map.

Indices

Indices are defined when mapping a type:

MongoRepository.Configure()
    // Every tenant should have their own Sales database
    .Database("Zoo", db => db
        .Map<Animal>("Animals", x => x
            .WithIndex(a => a.Species)
            .WithIndex(a => a.EnclosureNumber, unique: true)
            .WithIndex(a => a.LastVaccinationDate, sparse: true)
        )
        .Map<FeedingRoutine>("FeedingRoutines", x => x
            // Composite index
            .WithIndex(new { Composite index })
        )
    )
    .Build();

Capped collections

[To be documented]

Unconventional id properties

[To be documented]

DI frameworks

.NET Core

There is an extension package called JohnKnoop.MongoRepository.DotNetCoreDi that registers IRepository<T> as a dependency with the .NET Core dependency injection framework.

See the repository readme for more information.

Ninject

this.Bind(typeof(IRepository<>)).ToMethod(context =>
{
    Type entityType = context.GenericArguments[0];
    var mongoClient = context.Kernel.Get<IMongoClient>();
    var tenantKey = /* Pull out your tenent key from auth ticket or Owin context or what suits you best */;

    var getRepositoryMethod = typeof(MongoConfiguration).GetMethod(nameof(MongoConfiguration.GetRepository));
    var getRepositoryMethodGeneric = getRepositoryMethod.MakeGenericMethod(entityType);
    return getRepositoryMethodGeneric.Invoke(this, new object[] { mongoClient, tenantKey });
});

Design philosophy

This library is an extension to the MongoDB C# driver, and thus I don't mind exposing types from the MongoDB.Driver namespace, like IFindFluent or the result types of the various operations.

Any contributions to this library should be in line with the philosophy of this primarily being an extension that makes it easy to write multi-tenant applications using the MongoDB driver. I'm not looking to widen the scope of this library.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].