Browse Source

Implemented basic transaction API.

pull/112/head
Halil İbrahim Kalkan 9 years ago
parent
commit
637f533199
  1. 1
      src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
  2. 14
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs
  3. 14
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs
  4. 5
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs
  5. 50
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
  6. 46
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs
  7. 48
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs
  8. 15
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs
  9. 11
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs
  10. 31
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs
  11. 12
      src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs
  12. 13
      src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs
  13. 12
      src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs
  14. 2
      src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs
  15. 40
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs

1
src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj

@ -17,6 +17,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.0" />
</ItemGroup>
</Project>

14
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.EntityFrameworkCore
{
public static class DatabaseFacadeExtensions
{
public static bool IsRelational(this DatabaseFacade database)
{
return database.GetInfrastructure().GetService<IRelationalConnection>() != null;
}
}
}

14
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
namespace Volo.Abp.EntityFrameworkCore
{
internal static class DbContextExtensions
{
public static bool HasRelationalTransactionManager(this DbContext dbContext)
{
return dbContext.Database.GetService<IDbContextTransactionManager>() is IRelationalTransactionManager;
}
}
}

5
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs

@ -1,9 +1,10 @@
using System;
using System.Data.Common;
using System.Threading;
namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
{
internal class DbContextCreationContext
public class DbContextCreationContext
{
public static DbContextCreationContext Current => _current.Value;
private static readonly AsyncLocal<DbContextCreationContext> _current = new AsyncLocal<DbContextCreationContext>();
@ -12,6 +13,8 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
public string ConnectionString { get; }
public DbConnection ExistingConnection { get; set; }
public DbContextCreationContext(string connectionStringName, string connectionString)
{
ConnectionStringName = connectionStringName;

50
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs

@ -12,39 +12,37 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
public static DbContextOptions<TDbContext> Create<TDbContext>(IServiceProvider serviceProvider)
where TDbContext : AbpDbContext<TDbContext>
{
using (var scope = serviceProvider.CreateScope())
{
var creationContext = GetCreationContext<TDbContext>(scope.ServiceProvider);
var context = new AbpDbContextConfigurationContext<TDbContext>(
creationContext.ConnectionString,
creationContext.ConnectionStringName,
scope.ServiceProvider
);
var creationContext = GetCreationContext<TDbContext>(serviceProvider);
var dbContextOptions = GetDbContextOptions<TDbContext>(scope);
var context = new AbpDbContextConfigurationContext<TDbContext>(
creationContext.ConnectionString,
creationContext.ConnectionStringName,
serviceProvider
);
var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext));
if (configureAction != null)
{
((Action<AbpDbContextConfigurationContext<TDbContext>>)configureAction).Invoke(context);
}
else if (dbContextOptions.DefaultConfigureAction != null)
{
dbContextOptions.DefaultConfigureAction.Invoke(context);
}
else
{
throw new AbpException($"No configuration found for {typeof(DbContext).AssemblyQualifiedName}! Use services.Configure<AbpDbContextOptions>(...) to configure it.");
}
var dbContextOptions = GetDbContextOptions<TDbContext>(serviceProvider);
return context.DbContextOptions.Options;
var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext));
if (configureAction != null)
{
((Action<AbpDbContextConfigurationContext<TDbContext>>)configureAction).Invoke(context);
}
else if (dbContextOptions.DefaultConfigureAction != null)
{
dbContextOptions.DefaultConfigureAction.Invoke(context);
}
else
{
throw new AbpException($"No configuration found for {typeof(DbContext).AssemblyQualifiedName}! Use services.Configure<AbpDbContextOptions>(...) to configure it.");
}
return context.DbContextOptions.Options;
}
private static AbpDbContextOptions GetDbContextOptions<TDbContext>(IServiceScope scope) where TDbContext : AbpDbContext<TDbContext>
private static AbpDbContextOptions GetDbContextOptions<TDbContext>(IServiceProvider serviceProvider)
where TDbContext : AbpDbContext<TDbContext>
{
return scope.ServiceProvider.GetRequiredService<IOptions<AbpDbContextOptions>>().Value;
return serviceProvider.GetRequiredService<IOptions<AbpDbContextOptions>>().Value;
}
private static DbContextCreationContext GetCreationContext<TDbContext>(IServiceProvider serviceProvider)

46
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs

@ -0,0 +1,46 @@
using System.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
public class DbContextEfCoreTransactionStrategy : IEfCoreTransactionStrategy, ITransientDependency
{
public TDbContext CreateDbContext<TDbContext>(IUnitOfWork unitOfWork, DbContextCreationContext creationContext) where TDbContext : DbContext
{
var transactionApiKey = $"EntityFrameworkCore_{creationContext.ConnectionString}";
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as IEfCoreTransactionApi;
TDbContext dbContext;
if (activeTransaction == null)
{
dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
var dbtransaction = dbContext.Database.BeginTransaction((unitOfWork.Options.IsolationLevel ?? IsolationLevel.ReadUncommitted));
activeTransaction = new EfCoreTransactionApi(dbtransaction, dbContext);
unitOfWork.AddTransactionApi(transactionApiKey, activeTransaction);
}
else
{
creationContext.ExistingConnection = activeTransaction.DbContextTransaction.GetDbTransaction().Connection;
dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
if (dbContext.HasRelationalTransactionManager())
{
dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction());
}
else
{
dbContext.Database.BeginTransaction();
}
activeTransaction.AttendedDbContexts.Add(dbContext);
}
return dbContext;
}
}
}

48
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
public class EfCoreTransactionApi : IEfCoreTransactionApi
{
public IDbContextTransaction DbContextTransaction { get; }
public DbContext StarterDbContext { get; }
public List<DbContext> AttendedDbContexts { get; }
public EfCoreTransactionApi(IDbContextTransaction dbContextTransaction, DbContext starterDbContext)
{
DbContextTransaction = dbContextTransaction;
StarterDbContext = starterDbContext;
AttendedDbContexts = new List<DbContext>();
}
public void Commit()
{
DbContextTransaction.Commit();
foreach (var dbContext in AttendedDbContexts)
{
if (dbContext.HasRelationalTransactionManager())
{
continue; //Relational databases use the shared transaction
}
dbContext.Database.CommitTransaction();
}
}
public Task CommitAsync()
{
Commit();
return Task.CompletedTask;
}
public void Dispose()
{
DbContextTransaction.Dispose();
}
}
}

15
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
public interface IEfCoreTransactionApi : ITransactionApi
{
IDbContextTransaction DbContextTransaction { get; }
DbContext StarterDbContext { get; }
List<DbContext> AttendedDbContexts { get; }
}
}

11
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs

@ -0,0 +1,11 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
public interface IEfCoreTransactionStrategy
{
TDbContext CreateDbContext<TDbContext>(IUnitOfWork unitOfWork, DbContextCreationContext creationContext)
where TDbContext : DbContext;
}
}

31
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs

@ -1,3 +1,5 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
@ -5,18 +7,23 @@ using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
//TODO: Implement logic in DefaultDbContextResolver.Resolve in old ABP.
public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext>
where TDbContext : AbpDbContext<TDbContext>
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IConnectionStringResolver _connectionStringResolver;
private readonly IEfCoreTransactionStrategy _transactionStrategy;
public UnitOfWorkDbContextProvider(
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver)
IConnectionStringResolver connectionStringResolver,
IEfCoreTransactionStrategy transactionStrategy)
{
_unitOfWorkManager = unitOfWorkManager;
_connectionStringResolver = connectionStringResolver;
_transactionStrategy = transactionStrategy;
}
public TDbContext GetDbContext()
@ -41,12 +48,30 @@ namespace Volo.Abp.Uow.EntityFrameworkCore
return ((DbContextDatabaseApi<TDbContext>)databaseApi).DbContext;
}
private static TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
{
var creationContext = new DbContextCreationContext(connectionStringName, connectionString);
using (DbContextCreationContext.Use(creationContext))
{
return unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
TDbContext dbContext;
if (unitOfWork.Options.IsTransactional == true)
{
dbContext = _transactionStrategy.CreateDbContext<TDbContext>(unitOfWork, creationContext);
}
else
{
dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
}
if (unitOfWork.Options.Timeout.HasValue &&
dbContext.Database.IsRelational() &&
!dbContext.Database.GetCommandTimeout().HasValue)
{
dbContext.Database.SetCommandTimeout(unitOfWork.Options.Timeout.Value.TotalSeconds.To<int>());
}
return dbContext;
}
}
}

12
src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs

@ -83,7 +83,17 @@ namespace Volo.Abp.Uow
{
return _parent.GetOrAddDatabaseApi(key, factory);
}
public ITransactionApi FindTransactionApi(string key)
{
return _parent.FindTransactionApi(key);
}
public void AddTransactionApi(string key, ITransactionApi api)
{
_parent.AddTransactionApi(key, api);
}
public void Dispose()
{

13
src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs

@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace Volo.Abp.Uow
{
public interface ITransactionApi
{
void Commit();
Task CommitAsync();
void Dispose();
}
}

12
src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs

@ -0,0 +1,12 @@
using JetBrains.Annotations;
namespace Volo.Abp.Uow
{
public interface ITransactionApiContainer
{
[CanBeNull]
ITransactionApi FindTransactionApi([NotNull] string key);
void AddTransactionApi([NotNull] string key, [NotNull] ITransactionApi api);
}
}

2
src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs

@ -2,7 +2,7 @@
namespace Volo.Abp.Uow
{
public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer
public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer, ITransactionApiContainer
{
UnitOfWorkStartOptions Options { get; }

40
src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs

@ -35,6 +35,7 @@ namespace Volo.Abp.Uow
public IServiceProvider ServiceProvider { get; }
private readonly Dictionary<string, IDatabaseApi> _databaseApis;
private readonly Dictionary<string, ITransactionApi> _transactionApis;
private Exception _exception;
private bool _isCompleted;
@ -45,6 +46,7 @@ namespace Volo.Abp.Uow
ServiceProvider = serviceProvider;
_databaseApis = new Dictionary<string, IDatabaseApi>();
_transactionApis = new Dictionary<string, ITransactionApi>();
}
public void SaveChanges()
@ -72,6 +74,7 @@ namespace Volo.Abp.Uow
try
{
SaveChanges();
CommitTransactions();
OnCompleted();
}
catch (Exception ex)
@ -88,6 +91,7 @@ namespace Volo.Abp.Uow
try
{
await SaveChangesAsync(cancellationToken);
await CommitTransactionsAsync();
OnCompleted();
}
catch (Exception ex)
@ -110,6 +114,26 @@ namespace Volo.Abp.Uow
return _databaseApis.GetOrAdd(key, factory);
}
public ITransactionApi FindTransactionApi(string key)
{
Check.NotNull(key, nameof(key));
return _transactionApis.GetOrDefault(key);
}
public void AddTransactionApi(string key, ITransactionApi api)
{
Check.NotNull(key, nameof(key));
Check.NotNull(api, nameof(api));
if (_transactionApis.ContainsKey(key))
{
throw new AbpException("There is already a transaction API in this unit of work!");
}
_transactionApis.Add(key, api);
}
protected virtual void OnCompleted()
{
Completed.InvokeSafely(this);
@ -156,5 +180,21 @@ namespace Volo.Abp.Uow
{
return $"[UnitOfWork {Id}]";
}
protected virtual void CommitTransactions()
{
foreach (var transaction in _transactionApis.Values)
{
transaction.Commit();
}
}
protected virtual async Task CommitTransactionsAsync()
{
foreach (var transaction in _transactionApis.Values)
{
await transaction.CommitAsync();
}
}
}
}
Loading…
Cancel
Save