Introduction. I have found one question and one issue related to the problem :
elastic-db-tools github issue. 2015
stackoverflow question. 2015
Actually I work with multi-tenant architecture. Using latest .net core, ef core and ElasticScale. Most of the time I work with only one tenant - usual case. ShardMapManager provide SqlConnection, and then I can create DbContext using it.
Also I have the following case - I need to request more than one shard at a time. Something like - looking for a Customer through all tenants.
ElasticScale has MultiShardConnection for that. I have to write usual ADO.NET queries to solve this problem. I don't like ADO.NET queries for real. The whole solution works with the ORM - EF, thus I want to use ORM everywhere! I was trying to find something like an adapter for the EF...
Okay. I wrote the following solution:
public sealed class TenantDataContextFactory : ITenantDataContextFactory
{
private readonly Lazy<string> _cachedConnectionString;
private readonly IShardingService _shardingService;
private readonly IConfigurationManager _configurationManager;
private readonly ILogger<TenantDataContextFactory> _logger;
//sp_set_session_context required MSSQL SERVER 2016 or above!!!
private const string SpSetSessionContextQuery = #"exec sp_set_session_context #key=N'TenantId', #value=#TenantId";
public async Task<TenantDataContext> CreateAsync(Guid tenantId)
{
SqlConnection sqlConnection = null;
try
{
sqlConnection = await _shardingService.ShardMap
.OpenConnectionForKeyAsync(key: tenantId,
connectionString: _cachedConnectionString.Value,
options: ConnectionOptions.Validate)
.ConfigureAwait(false);
var cmd = sqlConnection.CreateCommand();
cmd.CommandText = SpSetSessionContextQuery;
cmd.Parameters.AddWithValue("#TenantId", tenantId);
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
return new TenantDataContext(tenantId, sqlConnection);
}
catch (Exception ex)
{
sqlConnection?.Dispose();
_logger.LogCritical(ex, $"Create(). Create {nameof(TenantDataContext)} for {tenantId} was ended with an error!");
throw;
}
}
}
public class MultiTenantConnectionFactory : IMultiTenantConnectionFactory
{
private readonly IShardingService _shardingService;
private readonly ITenantDataContextFactory _tenantDataContextFactory;
public async Task<IReadOnlyCollection<TenantDataContext>> GetContexts()
{
var shards = _shardingService.RegisteredTenants;
var connectionsTasks = shards.Select(x => _tenantDataContextFactory.CreateAsync(x));
return await Task.WhenAll(connectionsTasks).ConfigureAwait(false);
}
}
public sealed class MultiTenantRepository<T> : IMultiTenantRepository<T> where T : class, ITenantEntity
{
private readonly IMultiTenantConnectionFactory _multiTenantConnectionFactory;
public async Task<IList<T>> Find(Expression<Func<T, bool>> filter)
{
var dataContexts = await _multiTenantConnectionFactory.GetContexts().ConfigureAwait(false);
var tasksList = dataContexts.Select(async x =>
{
await using (x)
{
return await x.Set<T>().Where(filter).ToListAsync().ConfigureAwait(false);
}
});
var results = await Task.WhenAll(tasksList).ConfigureAwait(false);
return results.SelectMany(x => x).ToList();
}
}
But I don't like this solution! Managing connections, awaiting a lot of tasks... I think this solution is slow... Can anybody explain how should I work with the MultiShardConnection without ADO.NET?!
I'd like to test my solution and ADO.NET query performance. I think I will have to give up if I don't find another solution or use ADO.NET.
P.S. I know about ValueTask! I will change usual Task<T> to the ValueTask soon.
Related
i create an app with .NET 6.0 and Blazor Server. Sometimes i got an error in my application with message A second operation was started on this context instance before a previous operation completed.
Blazor Server and EF Core: A second operation was started on this context instance before a previous operation completed
I use normally pattern repository. I have repository for every table and unitofwork to save and other. In my previous question was explained me, that i use DbContext bad. Because it's shared dbcontext over repository and unit of work. I read many articles and usual recommendations was use only custom DbContext. I think it's not problem for me. I need to some changes in my framework but np. But i don't understand one thing. How i can do my custom generic queries for every dbset? Was there many articles about use repository with context factory and without unity of work. I don't really like the fact that all the operations are separate and access to database many times.
What is your recommendation and your expirience? Do you have any articles or tutorials about that?
I'm really just looking for how to make it the "best" and also usable for me.
Thank you very much :)
DbContext's are designed to be short-lived. Create a new instance of your context for each operation to resolve your issue.
Your DbContext would be something like this to make it easy to construct.
public class SomeDbContext : DbContext
{
private readonly IConfiguration configuration;
public SomeDbContext(IConfiguration configuration)
{
this.configuration = configuration;
// this.Database.Migrate(); <- optional
}
DbSet<SomeValue> SomeValues { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString =
this.configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
public async ValueTask<SomeValue> InsertSomeValueAsync(SomeValue someValue)
{
using var someDbContext = new SomeDbContext(this.configuration);
EntityEntry<SomeValue> entityEntry =
await someDbContext.SomeValues.AddAsync(entity: someValue);
await someDbContext.SaveChangesAsync();
return entityEntry.Entity;
}
}
The InsertSomeValueAsync method could easily be made generic.
public async ValueTask<T> InsertSomeValueAsync<T>(T someValue)
where T : class
{
using var someDbContext = new SomeDbContext(this.configuration);
EntityEntry<T> entityEntry =
await someDbContext.AddAsync(someValue);
await someDbContext.SaveChangesAsync();
return entityEntry.Entity;
}
One option is to use a DBContextFactory to manage the contexts.
Here's some example code from one of my applications.
Program
var dbConnectionString = builder.Configuration.GetValue<string>("MyConfiguration:ConnectionString");
builder.Services.AddDbContextFactory<MySqlServerDbContext>(options => options.UseSqlServer(dbConnectionString), ServiceLifetime.Singleton);
And using it in a (generic) data broker:
public class ServerDataBroker
: IDataBroker
{
private IDbContextFactory<MySqlServerDbContext> _dbContextFactory;
public ServerDataBroker(IDbContextFactory<MySqlServerDbContext> factory)
=> _dbContextFactory = factory;
//.....
public async ValueTask<int> GetRecordCountAsync<TRecord>() where TRecord : class, new()
{
using var context = _dbContextFactory.CreateDbContext();
var dbSet = context.Set<TRecord>();
return dbSet is not null
? await dbSet.CountAsync()
: 0;
}
//.....
There's an MS-Docs article about it here - https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/.
On testing, I use EF InMemory - you can use the Factory with it. Ask if you want to be pointed to some code.
This question asks about how to retrieve all users from AspNetCore Identity, in an async method:
ASP.NET Identity 2 UserManager get all users async
The non-sync method is simple:
[HttpGet]
public ActionResult<IEnumerable<UserDto>> GetAsync()
{
var users = userManager.Users
.ToList()
.Select(user => user.AsDto());
return Ok(users);
}
The obvious solution is this:
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDto>>> GetAsync()
{
var users = await userManager.Users
.ToListAsync()
.Select(user => user.AsDto());
return Ok(users);
}
But this doesn't work because userManager.Users is an IQueryable<>, and that doesn't define a .ToListAsync().
The recommended answer in the question above is:
public async Task<List<User>> GetUsersAsync()
{
using (var context = new YourContext())
{
return await UserManager.Users.ToListAsync();
}
}
But that is tied to Entity Framework, and if you're using AspNetCore.Identity.MongoDbCore, you won't have any dbcontexts, and the above simply doesn't work.
One comment on the answer points out that the .ToListAsync() extension method is defined in System.Data.Entity - but if you're using MongoDb you won't be including that.
How, actually, do you access all users from UserManager<> in an async method?
Basiccally, AspNetCore.Identity.MongoDbCore was just implementation wrapping around core concept of AspNetCore.Identity. And things gone just a bit difference from Sql implementation, cause they doesn't make use of unit of work concept (I know, SaveChange implement would wrap operation in transaction, but for everymethod call, the query execute immediately got this pretty similar to mongo in someway).
Then, If we need any furthur extension of those basic task, why don't just write our own implementation on top of default MongoDbCore implementation like good old days ?
// Starting with UserStore
public class ApplicationUserStore : MongoUserStore<How many generic would depend on use cases>, IApplicationUserStore
{
protected IMongoCollection<TUser> ApplicationUsersCollection;
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
ApplicationUsersCollection = Context.GetCollection<TUser>();
}
public async Task<ICollection<ApplicationUser>> GetAllUserAsync() => // We have access to UsersCollection, implement as normal mongodb operation;
}
// Then, UserManager
public class ApplicationUserManager : UserManager<How many generic would depend on use cases>
{
private readonly IApplicationUserStore _applicationUserStore;
// Constructtor that could resolve our ApplicationUserStore.
public ApplicationUserManager(IApplicationUserStore applicationUserStore,...)
{
_applicationUserStore = applicationUserStore;
}
public async Task<ICollection<ApplicationUser>> GetAllUserAsync() => _applicationUserStore.GetAllUserAsync();
// Registering things
services.AddScoped<IApplicationUserStore, ApplicationUserStore>();
services.AddScoped<ApplicationUserManager>();
}
This was just describe the idea, implementation should be vary.
I am writing to test FromSql Statement with InMemory Database. We are attempting to utilize Sqlite.
Running the following Sql passes the unit test without error.
select * from dbo.Product
However, doing this also passes with incorrect sql syntax. Would like to make the test fail with improper sql syntax. How can we test FromSql properly?
No error came from result of bad syntax .
seledg24g5ct * frofhm dbo.Product
Full Code:
namespace Tests.Services
{
public class ProductTest
{
private const string InMemoryConnectionString = "DataSource=:memory:";
private SqliteConnection _connection;
protected TestContext testContext;
public ProductServiceTest()
{
_connection = new SqliteConnection(InMemoryConnectionString);
_connection.Open();
var options = new DbContextOptionsBuilder<TestContext>()
.UseSqlite(_connection)
.Options;
testContext= new TestContext(options);
testContext.Database.EnsureCreated();
}
[Fact]
public async Task GetProductByIdShouldReturnResult()
{
var productList = testContext.Product
.FromSql($"seledg24g5ct * frofhm dbo.Product");
Assert.Equal(1, 1);
}
Using Net Core 3.1
There are two things to be taken into consideration here.
First, FromSql method is just a tiny bridge for using raw SQL queries in EF Core. No any validation/parsing of the passed SQL string occurs when the method is called except finding the parameter placeholders and associating db parameters with them. In order to get validated, it has to be executed.
Second, in order to support query composition over the FromSql result set, the method returns IQueryable<T>. Which means it is not executed immediately, but only if/when the result is enumerated. Which could happen when you use foreach loop over it, or call methods like ToList, ToArray or EF Core specific Load extension method, which is similar to ToList, but without creating list - the equivalent of foreach loop w/o body, e.g.
foreach (var _ in query) { }
With that being said, the code snippet
var productList = testContext.Product
.FromSql($"seledg24g5ct * frofhm dbo.Product");
does basically nothing, hence does not produce exception for invalid SQL. You must execute it using one of the aforementioned methods, e.g.
productList.Load();
or
var productList = testContext.Product
.FromSql($"seledg24g5ct * frofhm dbo.Product")
.ToList();
and assert the expected exception.
For more info, see Raw SQL Queries and How Queries Work sections of EF Core documentation.
#ivan-stoev has answered your question as to why your '.FromSql' statement does nothing - i.e. the query is never actually materialized. But to try and add some additional value, i'll share my Unit Test setup as it works well for me. Of course, YMMV.
Create a reusable class to handle generic In-memory database creation and easy population of tables with test data. NB: this requires the Nuget packages:
ServiceStack.OrmLite.Core
ServiceStack.OrmLite.Sqlite
I am using OrmLite as it allows for mocking and unit testing by providing a non-disposing connection factory which I can neatly inject into the Test classes via Dependency Injection:
/// <summary>
/// It is not possible to directly mock the Dapper commands i'm using to query the underlying database. There is a Nuget package called Moq.Dapper, but this approach doesnt need it.
/// It is not possible to mock In-Memory properties of a .NET Core DbContext such as the IDbConnection - i.e. the bit we actually want for Dapper queries.
/// for this reason, we need to use a different In-Memory database and load entities into it to query. Approach as per: https://mikhail.io/2016/02/unit-testing-dapper-repositories/
/// </summary>
public class TestInMemoryDatabase
{
private readonly OrmLiteConnectionFactory dbFactory =
new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider);
public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();
public void Insert<T>(IEnumerable<T> items)
{
using (var db = this.OpenConnection())
{
db.CreateTableIfNotExist<T>();
foreach (var item in items)
{
db.Insert(item);
}
}
}
}
A 'DbConnectionManager<EFContext>' class to provide the wrapper to the database connection using the EF Context you will already have created. This grabs the database connection from the EF Context and abstracts away the opening/closing operations:
public class DbConnectionManager<TContext> : IDbConnectionManager<TContext>, IDisposable
where TContext : DbContext
{
private TContext _context;
public DbConnectionManager(TContext context)
{
_context = context;
}
public async Task<IDbConnection> GetDbConnectionFromContextAsync()
{
var dbConnection = _context.Database.GetDbConnection();
if (dbConnection.State.Equals(ConnectionState.Closed))
{
await dbConnection.OpenAsync();
}
return dbConnection;
}
public void Dispose()
{
var dbConnection = _context.Database.GetDbConnection();
if (dbConnection.State.Equals(ConnectionState.Open))
{
dbConnection.Close();
}
}
}
Accompanying injectable Interface for the above:
public interface IDbConnectionManager<TContext>
where TContext : DbContext
{
Task<IDbConnection> GetDbConnectionFromContextAsync();
void Dispose();
}
In your .NET Project Startup class, register this interface with the inbuilt DI container (or whatever one you're using):
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IDbConnectionManager<>), typeof(DbConnectionManager<>));
}
Now our Unit Test class looks like this:
/// <summary>
/// All tests to follow the naming convention: MethodName_StateUnderTest_ExpectedBehaviour
/// </summary>
[ExcludeFromCodeCoverage]
public class ProductTests
{
//private static Mock<ILoggerAdapter<Db2DbViewAccess>> _logger;
//private static Mock<IOptions<AppSettings>> _configuration;
private readonly Mock<IDbConnectionManager<Db2Context>> _dbConnection;
private readonly List<Product> _listProducts = new List<Product>
{
new Product
{
Id = 1,
Name = "Product1"
},
new Product
{
Id = 2,
Name = "Product2"
},
new Product
{
Id = 3,
Name = "Product3"
},
};
public ProductTests()
{
//_logger = new Mock<ILoggerAdapter<Db2DbViewAccess>>();
//_configuration = new Mock<IOptions<AppSettings>>();
_dbConnection = new Mock<IDbConnectionManager<Db2Context>>();
}
[Fact]
public async Task GetProductAsync_ResultsFound_ReturnListOfAllProducts()
{
// Arrange
// Using a SQL Lite in-memory database to test the DbContext.
var testInMemoryDatabase = new TestInMemoryDatabase();
testInMemoryDatabase.Insert(_listProducts);
_dbConnection.Setup(c => c.GetDbConnectionFromContextAsync())
.ReturnsAsync(testInMemoryDatabase.OpenConnection());
//_configuration.Setup(x => x.Value).Returns(appSettings);
var productAccess = new ProductAccess(_configuration.Object); //, _logger.Object, _dbConnection.Object);
// Act
var result = await productAccess.GetProductAsync("SELECT * FROM Product");
// Assert
result.Count.Should().Equals(_listProducts.Count);
}
}
Notes on the above:
You can see i'm testing a 'ProductAccess' Data Access class which wraps my database calls but that should be easy enough to change for your setup. My ProductAccess class is expecting other services such as logging and Configuration to be injected in, but i have commented these out for this minimal example.
Note the setup of the in-memory database and populating it with your test list of entities to query is now a simple 2 lines (you could even do this just once in the Test class constructor if you want the same test dataset to use across tests):
var testInMemoryDatabase = new TestInMemoryDatabase();
testInMemoryDatabase.Insert(_listProducts);
Am attempting to delete all items from a sqlite database table accessed via nuget package sqlite-net-pcl(1.5.166-beta) for Xamarin.forms.
The method DeleteAllAsync, according to Visual Studio's code completion menu,
takes a 'TableMapping Map' Can you help me find out what a 'TableMapping Map' is in this case?
Where could I find the documentation for the sqlite-net-pcl nuget package? There is no project page listed for it on nuget.
The code snippet below gives context and an incorrect call of DeleteAllAsync
public class ItemDb
{
readonly SQLiteAsyncConnection database;
public ItemDb(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
database.CreateTableAsync<Item>().Wait();
}
internal void DeleteAll()
{
database.DeleteAllAsync(database.Table<Item>);
}
}
You just need to specify the Type to use DeleteAllAsync, and it will remove all items from the table.
internal async Task DeleteAll()
{
await database.DeleteAllAsync<Item>();
}
DeleteAllAsync is new in sqlite-net-pcl v1.5, and v1.5 is still in pre-release. The official documentation is here, and it will likely be updated to include DeleteAllAsync once v1.5 is promoted to stable:
https://github.com/praeclarum/sqlite-net/wiki
Faster Method
A faster way to delete all items in a table is to drop the table and re-create it (assuming the tables has many items).
internal async Task DeleteAll()
{
await database.DropTableAsync<Item>();
await database.CreateTableAsync<Item>();
}
Complete Example
Here is a complete example that helps improve performance and avoid multithreading problems.
public class ItemDb
{
readonly SQLiteAsyncConnection database;
readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
public ItemDb(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
}
internal async Task DeleteAllItems()
{
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
await Initialize<Item>().ConfigureAwait(false);
try
{
await database.DropTableAsync<Item>().ConfigureAwait(false);
await database.CreateTableAsync<Item>().ConfigureAwait(false);
}
finally
{
semaphoreSlim.Release();
}
}
async Task Initialize<T>()
{
if (!DatabaseConnection.TableMappings.Any(x => x.MappedType.Name == typeof(T).Name))
{
await DatabaseConnection.EnableWriteAheadLoggingAsync().ConfigureAwait(false);
await DatabaseConnection.CreateTablesAsync(CreateFlags.None, typeof(T)).ConfigureAwait(false);
}
}
}
I know that MongoDB is not supposed to support unit of work, etc. But I think it would be nice to implement the repository which would store only the intentions (similar to criteria) and then commit them to the DB. Otherwise in every method in your repository you have to create connection to DB and then close it. If we place the connection to DB in some BaseRepository class, then we tie our repository to concrete DB and it is really difficult to test repositories, to test IoC which resolve repositories.
Is creating a session in MongoDB a bad idea? Is there a way to separate the connection logic from repository?
Here is some code by Rob Conery. Is it a good idea to always connect to your DB on every request? What is the best practice?
There is one more thing. Imagine I want to provide an index for a collection. Previously I did in a constructor but with Rob's approach this seems out of logic to do it there.
using Norm;
using Norm.Responses;
using Norm.Collections;
using Norm.Linq;
public class MongoSession {
private string _connectionString;
public MongoSession() {
//set this connection as you need. This is left here as an example, but you could, if you wanted,
_connectionString = "mongodb://127.0.0.1/MyDatabase?strict=false";
}
public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() {
//not efficient, NoRM should do this in a way that sends a single command to MongoDB.
var items = All<T>().Where(expression);
foreach (T item in items) {
Delete(item);
}
}
public void Delete<T>(T item) where T : class, new() {
using(var db = Mongo.Create(_connectionString))
{
db.Database.GetCollection<T>().Delete(item);
}
}
public void DeleteAll<T>() where T : class, new() {
using(var db = Mongo.Create(_connectionString))
{
db.Database.DropCollection(typeof(T).Name);
}
}
public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() {
T retval = default(T);
using(var db = Mongo.Create(_connectionString))
{
retval = db.GetCollection<T>().AsQueryable()
.Where(expression).SingleOrDefault();
}
return retval;
}
public IQueryable<T> All<T>() where T : class, new() {
//don't keep this longer than you need it.
var db = Mongo.Create(_connectionString);
return db.GetCollection<T>().AsQueryable();
}
public void Add<T>(T item) where T : class, new() {
using(var db = Mongo.Create(_connectionString))
{
db.GetCollection<T>().Insert(item);
}
}
public void Add<T>(IEnumerable<T> items) where T : class, new() {
//this is WAY faster than doing single inserts.
using(var db = Mongo.Create(_connectionString))
{
db.GetCollection<T>().Insert(items);
}
}
public void Update<T>(T item) where T : class, new() {
using(var db = Mongo.Create(_connectionString))
{
db.GetCollection<T>().UpdateOne(item, item);
}
}
//this is just some sugar if you need it.
public T MapReduce<T>(string map, string reduce) {
T result = default(T);
using(var db = Mongo.Create(_connectionString))
{
var mr = db.Database.CreateMapReduce();
MapReduceResponse response =
mr.Execute(new MapReduceOptions(typeof(T).Name) {
Map = map,
Reduce = reduce
});
MongoCollection<MapReduceResult<T>> coll = response.GetCollection<MapReduceResult<T>>();
MapReduceResult<T> r = coll.Find().FirstOrDefault();
result = r.Value;
}
return result;
}
public void Dispose() {
_server.Dispose();
}
}
Don't worry too much about opening and closing connections. The MongoDB C# driver maintains an internal connection pool, so you won't suffer overheads of opening and closing actual connections each time you create a new MongoServer object.
You can create a repository interface that exposes your data logic, and build a MongoDB implementation that is injected where it's needed. That way, the MongoDB specific connection code is abstratced away from your application, which only sees the IRepository.
Be careful trying to implement a unit-of-work type pattern with MongoDB. Unlike SQL Server, you can't enlist multiple queries in a transaction that can be rolled back if one fails.
For a simple example of a repository pattern that has MongoDB, SQL Server and JSON implementations, check out the NBlog storage code. It uses Autofac IoC to inject concrete repositories into an ASP.NET MVC app.
While researching design patterns, I was creating a basic repository pattern for .Net Core and MongoDB. While reading over the MongoDB documentation I came across an article about transactions in MongoDB. In the article it specified that:
Starting in version 4.0, MongoDB provides the ability to perform
multi-document transactions against replica sets.
Looking around the intertubes I came across a library that does a really good job of implementing the Unit of Work pattern for MongoDB.
If you are interested in an implementation similar to Rob Connery's and NBlog storage code but using the mongodb csharp driver 2.0 (that is asynchronous), you can look at:
https://github.com/alexandre-spieser/mongodb-generic-repository
You can then write a custom repository inheriting from BaseMongoRepository.
public interface ITestRepository : IBaseMongoRepository
{
void DropTestCollection<TDocument>();
void DropTestCollection<TDocument>(string partitionKey);
}
public class TestRepository : BaseMongoRepository, ITestRepository
{
public TestRepository(string connectionString, string databaseName) : base(connectionString, databaseName)
{
}
public void DropTestCollection<TDocument>()
{
MongoDbContext.DropCollection<TDocument>();
}
public void DropTestCollection<TDocument>(string partitionKey)
{
MongoDbContext.DropCollection<TDocument>(partitionKey);
}
}
Briefly
You can use this nuget-package UnitOfWork.MongoDb. This is a wrapper for MongoDb.Driver with some helpful functions and features. Also you can find sample code and video (ru).
Read settings for connection
// read MongoDb settings from appSettings.json
services.AddUnitOfWork(configuration.GetSection(nameof(DatabaseSettings)));
// --- OR ----
// use hardcoded
services.AddUnitOfWork(config =>
{
config.Credential = new CredentialSettings { Login = "sa", Password = "password" };
config.DatabaseName = "MyDatabase";
config.Hosts = new[] { "Localhost" };
config.MongoDbPort = 27017;
config.VerboseLogging = false;
});
Injections
namespace WebApplicationWithMongo.Pages
{
public class IndexModel : PageModel
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<IndexModel> _logger;
public IndexModel(IUnitOfWork unitOfWork, ILogger<IndexModel> logger)
{
_unitOfWork = unitOfWork;
_logger = logger;
}
public IPagedList<Order>? Data { get; set; }
}
}
After injection you can get repository.
Get repository
public async Task<IActionResult> OnGetAsync(int pageIndex = 0, int pageSize = 10)
{
var repository = _unitOfWork.GetRepository<Order, int>();
Data = await repository.GetPagedAsync(pageIndex, pageSize, FilterDefinition<Order>.Empty, HttpContext.RequestAborted);
return Page();
}
GetPagedAsync one of some helpful implementations
Transactions
If you need ACID operations (transactions) you can use IUnitOfWork something like this. (Replicate Set should be correctly set up). For example:
await unitOfWork.UseTransactionAsync<OrderBase, int>(ProcessDataInTransactionAsync1, HttpContext.RequestAborted, session);
Method ProcessDataInTransactionAsync1 can be looks like this:
async Task ProcessDataInTransactionAsync1(IRepository<OrderBase, int> repositoryInTransaction, IClientSessionHandle session, CancellationToken cancellationToken)
{
await repository.Collection.DeleteManyAsync(session, FilterDefinition<OrderBase>.Empty, null, cancellationToken);
var internalOrder1 = DocumentHelper.GetInternal(99);
await repositoryInTransaction.Collection.InsertOneAsync(session, internalOrder1, null, cancellationToken);
logger!.LogInformation("InsertOne: {item1}", internalOrder1);
var internalOrder2 = DocumentHelper.GetInternal(100);
await repositoryInTransaction.Collection.InsertOneAsync(session, internalOrder2, null, cancellationToken);
logger!.LogInformation("InsertOne: {item2}", internalOrder2);
var filter = Builders<OrderBase>.Filter.Eq(x => x.Id, 99);
var updateDefinition = Builders<OrderBase>.Update.Set(x => x.Description, "Updated description");
var result = await repositoryInTransaction.Collection
.UpdateOneAsync(session, filter, updateDefinition, new UpdateOptions { IsUpsert = false }, cancellationToken);
if (result.IsModifiedCountAvailable)
{
logger!.LogInformation("Update {}", result.ModifiedCount);
}
throw new ApplicationException("EXCEPTION! BANG!");
}
This nuget is open-source Calabonga.UnitOfWork.MongoDb