Skip to content

Commit

Permalink
Add Transaction Support and Upgrade MongoDriver to V3
Browse files Browse the repository at this point in the history
  • Loading branch information
litenova committed Nov 5, 2024
1 parent a5318d9 commit bd45e8c
Show file tree
Hide file tree
Showing 22 changed files with 426 additions and 86 deletions.
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 2
updates:

# Maintain dependencies for Nuget Packages
- package-ecosystem: nuget
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 25

# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>A. Shafie</Authors>
<PackageTags>MongoDB;AspNetCore;MongoDbDriverExtensions</PackageTags>
<VersionPrefix>1.2.0</VersionPrefix>
<VersionPrefix>2.0.0</VersionPrefix>
<PackageProjectUrl>https://github.com/litenova/Myrtle</PackageProjectUrl>
<RepositoryUrl>https://github.com/litenova/Myrtle</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
4 changes: 3 additions & 1 deletion Myrtle.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution-items", "solution-
global.json = global.json
LICENSE = LICENSE
README.md = README.md
Release-Notes.txt = Release-Notes.txt
.editorconfig = .editorconfig
EndProjectSection
EndProject
Expand All @@ -23,6 +22,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logo", "logo", "{C2B92B88-1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{7834C293-9FFA-451E-BC61-56A30F1D1115}"
ProjectSection(SolutionItems) = preProject
.github\dependabot.yml = .github\dependabot.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{F648E961-9787-4E90-A842-212E08F3C8E2}"
ProjectSection(SolutionItems) = preProject
Expand Down
56 changes: 47 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@

![Myrtle Logo](https://raw.githubusercontent.com/litenova/Myrtle/main/assets/logo/logo-128x128.png)

Myrtle is a comprehensive collection of useful extensions and configurations for the official MongoDB C# driver. It aims to simplify and enhance the experience of working with MongoDB in .NET applications.
Myrtle is a comprehensive collection of useful extensions and configurations for the official MongoDB C# driver. It aims
to simplify and enhance the experience of working with MongoDB in .NET applications.

[![Build Status](https://github.com/litenova/Myrtle/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/litenova/Myrtle/actions/workflows/ci-cd.yml)
[![Coverage Status](https://coveralls.io/repos/github/litenova/Myrtle/badge.svg?branch=main)](https://coveralls.io/github/litenova/Myrtle?branch=main)

## Features

- **Enhanced Configuration**: Simplified setup for MongoDB with various conventions and serialization options.
- **Dependency Injection**: Easy integration with Microsoft.Extensions.DependencyInjection for ASP.NET Core applications.
- **Dependency Injection**: Easy integration with Microsoft.Extensions.DependencyInjection for ASP.NET Core
applications.
- **Repository Pattern**: Generic repository implementation for streamlined data access.
- **Data Protection**: Support for storing ASP.NET Core Data Protection keys in MongoDB.
- **Extensible Architecture**: Modular design allowing for easy addition of new features and configurations.

## Packages

| Package | Version | Description |
|---------|---------|-------------|
| Myrtle.Abstractions | [![NuGet](https://img.shields.io/nuget/v/Myrtle.Abstractions.svg)](https://www.nuget.org/packages/Myrtle.Abstractions/) | Core abstractions and interfaces for Myrtle |
| Myrtle | [![NuGet](https://img.shields.io/nuget/v/Myrtle.svg)](https://www.nuget.org/packages/Myrtle/) | Main implementation of Myrtle extensions and configurations |
| Myrtle.Extensions.MicrosoftDependencyInjection | [![NuGet](https://img.shields.io/nuget/v/Myrtle.Extensions.MicrosoftDependencyInjection.svg)](https://www.nuget.org/packages/Myrtle.Extensions.MicrosoftDependencyInjection/) | Integration with Microsoft.Extensions.DependencyInjection |
| Myrtle.AspNetCore.DataProtection.Keys | [![NuGet](https://img.shields.io/nuget/v/Myrtle.AspNetCore.DataProtection.Keys.svg)](https://www.nuget.org/packages/Myrtle.AspNetCore.DataProtection.Keys/) | Support for storing ASP.NET Core Data Protection keys in MongoDB |
| Package | Version | Description |
|------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
| Myrtle.Abstractions | [![NuGet](https://img.shields.io/nuget/v/Myrtle.Abstractions.svg)](https://www.nuget.org/packages/Myrtle.Abstractions/) | Core abstractions and interfaces for Myrtle |
| Myrtle | [![NuGet](https://img.shields.io/nuget/v/Myrtle.svg)](https://www.nuget.org/packages/Myrtle/) | Main implementation of Myrtle extensions and configurations |
| Myrtle.Extensions.MicrosoftDependencyInjection | [![NuGet](https://img.shields.io/nuget/v/Myrtle.Extensions.MicrosoftDependencyInjection.svg)](https://www.nuget.org/packages/Myrtle.Extensions.MicrosoftDependencyInjection/) | Integration with Microsoft.Extensions.DependencyInjection |
| Myrtle.AspNetCore.DataProtection.Keys | [![NuGet](https://img.shields.io/nuget/v/Myrtle.AspNetCore.DataProtection.Keys.svg)](https://www.nuget.org/packages/Myrtle.AspNetCore.DataProtection.Keys/) | Support for storing ASP.NET Core Data Protection keys in MongoDB |

## Installation

Expand All @@ -44,6 +46,7 @@ Myrtle provides several key interfaces and abstractions to simplify working with
- `IMongoRepository<TDocument, TId>`: Defines a generic repository pattern for MongoDB operations.
- `IMongoConfigurationRegistry`: Allows registration of custom MongoDB configurations.
- `IMongoConfiguration`: Represents a specific MongoDB configuration.
- `IMongoTransactionContext`: Represents a MongoDB transaction context.

## Usage

Expand Down Expand Up @@ -143,6 +146,38 @@ public class UserService
}
```

### Transaction Management with `IMongoTransactionContext`

The `IMongoTransactionContext` interface allows you to manage MongoDB transactions easily. Here's a simple example of
how to start, commit, and abort a transaction:

1. **Start a Transaction:**
```csharp
public async Task PerformTransactionAsync(IMongoTransactionContext transactionContext)
{
await transactionContext.StartAsync();
}
```

2. **Commit a Transaction:**
```csharp
public async Task CompleteTransactionAsync(IMongoTransactionContext transactionContext)
{
await transactionContext.CommitAsync();
}
```

3. **Abort a Transaction:**
```csharp
public async Task CancelTransactionAsync(IMongoTransactionContext transactionContext)
{
await transactionContext.AbortAsync();
}
```

Use these methods within a service or application logic to effectively manage transactional operations with MongoDB,
ensuring data consistency and reliability.

### Data Protection Key Storage

To configure ASP.NET Core Data Protection to store keys in MongoDB:
Expand All @@ -158,4 +193,7 @@ Myrtle is licensed under the MIT License. See the [LICENSE](LICENSE) file for de

## Support

If you encounter any issues or have questions, please [open an issue](https://github.com/litenova/Myrtle/issues) on GitHub.
If you encounter any issues or have questions, please [open an issue](https://github.com/litenova/Myrtle/issues) on
GitHub.


28 changes: 0 additions & 28 deletions Release-Notes.txt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Myrtle.Abstractions.Exceptions;

/// <summary>
/// The exception thrown when attempting to start a MongoDB transaction that is already active.
/// </summary>
public sealed class MongoTransactionAlreadyStartedException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="MongoTransactionAlreadyStartedException"/> class.
/// </summary>
public MongoTransactionAlreadyStartedException()
: base("A MongoDB transaction is already in progress.")
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Myrtle.Abstractions.Exceptions;

/// <summary>
/// The exception thrown when attempting to perform operations on a MongoDB transaction that hasn't been started.
/// </summary>
public sealed class MongoTransactionNotStartedException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="MongoTransactionNotStartedException"/> class.
/// </summary>
public MongoTransactionNotStartedException()
: base("No MongoDB transaction has been started. Call StartAsync before performing transaction operations.")
{
}
}
75 changes: 75 additions & 0 deletions src/Myrtle.Abstractions/IMongoTransactionContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
namespace Myrtle.Abstractions;

using MongoDB.Driver;
using Exceptions;

/// <summary>
/// Represents a context for managing the lifecycle of a MongoDB transaction.
/// Ensures proper handling of MongoDB transactions, including starting, committing, and cleaning up resources.
/// Implements IAsyncDisposable for automatic cleanup of transaction resources.
/// </summary>
/// <remarks>
/// This context should be used within a using block to ensure proper resource cleanup.
/// All operations within the transaction should be performed between calls to StartAsync and CommitAsync.
/// If an exception occurs, the transaction should be aborted using AbortAsync.
/// </remarks>
public interface IMongoTransactionContext : IAsyncDisposable
{
/// <summary>
/// Gets a value indicating whether a transaction is currently active within this context.
/// </summary>
/// <value>
/// True if a transaction has been started and not yet committed or aborted; otherwise, false.
/// </value>
bool IsActive { get; }

/// <summary>
/// Gets the current MongoDB session handle associated with this transaction context.
/// </summary>
/// <value>
/// The MongoDB client session handle managing the current transaction.
/// </value>
/// <exception cref="MongoTransactionNotStartedException">
/// Thrown when accessing the session before starting a transaction.
/// </exception>
IClientSessionHandle Session { get; }

/// <summary>
/// Starts a new MongoDB transaction within this context.
/// </summary>
/// <param name="cancellationToken">A token to cancel the async operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="MongoTransactionAlreadyStartedException">
/// Thrown when attempting to start a transaction while another is already active.
/// </exception>
/// <exception cref="MongoException">
/// Thrown when the MongoDB server encounters an error while starting the transaction.
/// </exception>
Task StartAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Commits the current MongoDB transaction.
/// </summary>
/// <param name="cancellationToken">A token to cancel the async operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="MongoTransactionNotStartedException">
/// Thrown when attempting to commit a transaction that hasn't been started.
/// </exception>
/// <exception cref="MongoException">
/// Thrown when the MongoDB server encounters an error while committing the transaction.
/// </exception>
Task CommitAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Aborts the current MongoDB transaction.
/// </summary>
/// <param name="cancellationToken">A token to cancel the async operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="MongoTransactionNotStartedException">
/// Thrown when attempting to abort a transaction that hasn't been started.
/// </exception>
/// <exception cref="MongoException">
/// Thrown when the MongoDB server encounters an error while aborting the transaction.
/// </exception>
Task AbortAsync(CancellationToken cancellationToken = default);
}
2 changes: 1 addition & 1 deletion src/Myrtle.Abstractions/Myrtle.Abstractions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.27.0"/>
<PackageReference Include="MongoDB.Driver" Version="3.0.0"/>
</ItemGroup>

</Project>
34 changes: 29 additions & 5 deletions src/Myrtle.Abstractions/Repositories/IMongoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,55 @@ public interface IMongoRepository<TDocument, TId> where TDocument : class
/// </summary>
/// <param name="id">The identifier of the document to retrieve.</param>
/// <param name="cancellationToken">A token to cancel the operation if needed.</param>
/// <returns>The retrieved document, or null if not found.</returns>
/// <returns>A task that represents the asynchronous operation, containing the retrieved document, or null if not found.</returns>
Task<TDocument?> GetByIdAsync(TId id, CancellationToken cancellationToken = default);

/// <summary>
/// Adds a new document to the collection.
/// </summary>
/// <param name="document">The document to add.</param>
/// <param name="cancellationToken">A token to cancel the operation if needed.</param>
/// <returns>The added document, potentially with server-generated ID.</returns>
Task<TDocument> AddAsync(TDocument document, CancellationToken cancellationToken = default);
/// <returns>A task that represents the asynchronous operation.</returns>
Task AddAsync(TDocument document, CancellationToken cancellationToken = default);

/// <summary>
/// Adds multiple documents to the collection.
/// </summary>
/// <param name="documents">The documents to add.</param>
/// <param name="cancellationToken">A token to cancel the operation if needed.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task AddManyAsync(IEnumerable<TDocument> documents, CancellationToken cancellationToken = default);

/// <summary>
/// Updates an existing document in the collection.
/// </summary>
/// <param name="id">The identifier of the document to update.</param>
/// <param name="document">The updated document.</param>
/// <param name="cancellationToken">A token to cancel the operation if needed.</param>
/// <returns>A task representing the asynchronous update operation.</returns>
/// <returns>A task that represents the asynchronous update operation.</returns>
Task UpdateAsync(TId id, TDocument document, CancellationToken cancellationToken = default);

/// <summary>
/// Updates multiple documents in the collection.
/// </summary>
/// <param name="updates">A dictionary containing the IDs and documents to update.</param>
/// <param name="cancellationToken">A token to cancel the operation if needed.</param>
/// <returns>A task that represents the asynchronous update operation.</returns>
Task UpdateManyAsync(IDictionary<TId, TDocument> updates, CancellationToken cancellationToken = default);

/// <summary>
/// Deletes a document from the collection.
/// </summary>
/// <param name="id">The identifier of the document to delete.</param>
/// <param name="cancellationToken">A token to cancel the operation if needed.</param>
/// <returns>A task representing the asynchronous delete operation.</returns>
/// <returns>A task that represents the asynchronous delete operation.</returns>
Task DeleteAsync(TId id, CancellationToken cancellationToken = default);

/// <summary>
/// Deletes multiple documents from the collection.
/// </summary>
/// <param name="ids">The identifiers of the documents to delete.</param>
/// <param name="cancellationToken">A token to cancel the operation if needed.</param>
/// <returns>A task that represents the asynchronous delete operation.</returns>
Task DeleteManyAsync(IEnumerable<TId> ids, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public MongoDbXmlRepository(IMongoCollection<DataProtectionKey> keyCollection, I
public IReadOnlyCollection<XElement> GetAllElements()
{
return _keyCollection.AsQueryable()
.ToEnumerable()
.AsEnumerable()
.Select(key =>
{
_logger.ReadingKeyFromElement(key.FriendlyName, key.Xml);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="MongoDB.Driver" Version="2.27.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2"/>
<PackageReference Include="MongoDB.Driver" Version="3.0.0"/>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2"/>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ private static IServiceCollection AddMongoDBCore(this IServiceCollection service

services.AddScoped(typeof(IMongoCollectionContext<>), typeof(MongoCollectionContext<>));
services.AddScoped(typeof(IMongoRepository<,>), typeof(MongoRepository<,>));
services.AddScoped<IMongoTransactionContext, MongoTransactionContext>();

return services;
}
Expand Down
Loading

0 comments on commit bd45e8c

Please sign in to comment.