Skip to content

Commit

Permalink
feat(efcore): support DB transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
skarllot committed Sep 7, 2023
1 parent 5750d40 commit c8a93ce
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 4 deletions.
6 changes: 6 additions & 0 deletions src/Expressions.EntityFrameworkCore/Sessions/EfDbSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public EfDbSession(
public TContext Context { get; }
public ChangeTracking Tracking { get; }

public async ValueTask<IDbSessionTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default)
{
var transaction = await Context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
return new EfDbSessionTransaction(transaction);
}

public void Add<TEntity>(TEntity entity)
where TEntity : class => Context.Add(entity);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore.Storage;
using Raiqub.Expressions.Sessions;

namespace Raiqub.Expressions.EntityFrameworkCore.Sessions;

public sealed class EfDbSessionTransaction : IDbSessionTransaction
{
private readonly IDbContextTransaction _contextTransaction;

public EfDbSessionTransaction(IDbContextTransaction contextTransaction) =>
_contextTransaction = contextTransaction;

public Task CommitAsync(CancellationToken cancellationToken = default) =>
_contextTransaction.CommitAsync(cancellationToken);

public ValueTask DisposeAsync() =>
_contextTransaction.DisposeAsync();

public void Dispose() =>
_contextTransaction.Dispose();
}
6 changes: 6 additions & 0 deletions src/Expressions.Marten/Sessions/MartenDbSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public MartenDbSession(ILogger<MartenDbSession> logger, IDocumentSession session

public ChangeTracking Tracking { get; }

public ValueTask<IDbSessionTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default)
{
throw new NotSupportedException(
"Transactions are not currently supported by Marten implementation of IDbSession");
}

public void Add<TEntity>(TEntity entity)
where TEntity : class => AddRange(new[] { entity });

Expand Down
13 changes: 12 additions & 1 deletion src/Expressions.Writing/Sessions/IDbSession.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Raiqub.Expressions.Sessions;
using System.Diagnostics.Contracts;

namespace Raiqub.Expressions.Sessions;

/// <summary>Represents a session used to perform data access operations.</summary>
public interface IDbSession : IDbQuerySession
Expand All @@ -9,6 +11,15 @@ public interface IDbSession : IDbQuerySession
/// </remarks>
ChangeTracking Tracking { get; }

/// <summary>Starts a new transaction.</summary>
/// <param name="cancellationToken">A token to observe for cancellation requests.</param>
/// <returns>
/// A task that represents the asynchronous transaction initialization.
/// The task result contains a <see cref="IDbSessionTransaction"/> that represents the started transaction.
/// </returns>
[Pure]
ValueTask<IDbSessionTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default);

/// <summary>Tracks the specified entity as added.</summary>
/// <typeparam name="TEntity">The type of entity to add.</typeparam>
/// <param name="entity">The entity to add.</param>
Expand Down
9 changes: 9 additions & 0 deletions src/Expressions.Writing/Sessions/IDbSessionTransaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Raiqub.Expressions.Sessions;

public interface IDbSessionTransaction : IAsyncDisposable, IDisposable
{
/// <summary>Commits all changes made to the database in the current transaction.</summary>
/// <param name="cancellationToken">A token to observe for cancellation requests.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
Task CommitAsync(CancellationToken cancellationToken = default);
}
4 changes: 2 additions & 2 deletions tests/Common.Tests/Sessions/SessionFactoryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ public async Task RemoveAndSaveShouldCommitChanges()
blogs.Should().NotContain(blog => blog.Name == "Second");
}

private IDbSessionFactory CreateSessionFactory() => ServiceProvider.GetRequiredService<IDbSessionFactory>();
protected IDbSessionFactory CreateSessionFactory() => ServiceProvider.GetRequiredService<IDbSessionFactory>();

private static IEnumerable<Blog> GetBlogs()
protected static IEnumerable<Blog> GetBlogs()
{
DateTimeOffset now = DateTimeOffset.UtcNow;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Raiqub.Common.Tests;
using Raiqub.Common.Tests.Examples;
using Raiqub.Common.Tests.Sessions;
using Raiqub.Expressions.EntityFrameworkCore.Tests.Examples;
using Raiqub.Expressions.Sessions;

namespace Raiqub.Expressions.EntityFrameworkCore.Tests.Sessions;

Expand All @@ -27,4 +30,47 @@ public EfSessionFactoryTest(PostgreSqlFixture fixture)
public Task InitializeAsync() => _fixture.SnapshotDatabaseAsync();

public Task DisposeAsync() => _fixture.ResetDatabaseAsync();

[Fact]
public async Task AddAndSaveOnUncommittedTransactionShouldNotCommitChanges()
{
var sessionFactory = CreateSessionFactory();

await using (var session = sessionFactory.Create())
await using (await session.BeginTransactionAsync())
{
await session.AddAsync(GetBlogs().First());
await session.SaveChangesAsync();
}

int count;
await using (var session = sessionFactory.Create())
{
count = await session.Query<Blog>().CountAsync();
}

count.Should().Be(0);
}

[Fact]
public async Task AddAndSaveOnCommittedTransactionShouldCommitChanges()
{
var sessionFactory = CreateSessionFactory();

await using (var session = sessionFactory.Create())
await using (var transaction = await session.BeginTransactionAsync())
{
await session.AddAsync(GetBlogs().First());
await session.SaveChangesAsync();
await transaction.CommitAsync();
}

int count;
await using (var session = sessionFactory.Create())
{
count = await session.Query<Blog>().CountAsync();
}

count.Should().Be(1);
}
}

0 comments on commit c8a93ce

Please sign in to comment.