diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj b/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
index 252c61d826..6f419752a4 100644
--- a/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
+++ b/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
@@ -17,6 +17,7 @@
+
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs
new file mode 100644
index 0000000000..f663f43a0d
--- /dev/null
+++ b/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() != null;
+ }
+ }
+}
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs
new file mode 100644
index 0000000000..3366186349
--- /dev/null
+++ b/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() is IRelationalTransactionManager;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs
index b8a713c8aa..2b65b199a1 100644
--- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs
+++ b/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 _current = new AsyncLocal();
@@ -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;
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
index 047c10ba6c..c4c5ef40c4 100644
--- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
+++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
@@ -12,39 +12,37 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
public static DbContextOptions Create(IServiceProvider serviceProvider)
where TDbContext : AbpDbContext
{
- using (var scope = serviceProvider.CreateScope())
- {
- var creationContext = GetCreationContext(scope.ServiceProvider);
-
- var context = new AbpDbContextConfigurationContext(
- creationContext.ConnectionString,
- creationContext.ConnectionStringName,
- scope.ServiceProvider
- );
+ var creationContext = GetCreationContext(serviceProvider);
- var dbContextOptions = GetDbContextOptions(scope);
+ var context = new AbpDbContextConfigurationContext(
+ creationContext.ConnectionString,
+ creationContext.ConnectionStringName,
+ serviceProvider
+ );
- var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext));
- if (configureAction != null)
- {
- ((Action>)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(...) to configure it.");
- }
+ var dbContextOptions = GetDbContextOptions(serviceProvider);
- return context.DbContextOptions.Options;
+ var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext));
+ if (configureAction != null)
+ {
+ ((Action>)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(...) to configure it.");
}
+
+ return context.DbContextOptions.Options;
}
- private static AbpDbContextOptions GetDbContextOptions(IServiceScope scope) where TDbContext : AbpDbContext
+ private static AbpDbContextOptions GetDbContextOptions(IServiceProvider serviceProvider)
+ where TDbContext : AbpDbContext
{
- return scope.ServiceProvider.GetRequiredService>().Value;
+ return serviceProvider.GetRequiredService>().Value;
}
private static DbContextCreationContext GetCreationContext(IServiceProvider serviceProvider)
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs
new file mode 100644
index 0000000000..b1da0f8a71
--- /dev/null
+++ b/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(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();
+ 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();
+
+ if (dbContext.HasRelationalTransactionManager())
+ {
+ dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction());
+ }
+ else
+ {
+ dbContext.Database.BeginTransaction();
+ }
+
+ activeTransaction.AttendedDbContexts.Add(dbContext);
+ }
+
+ return dbContext;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs
new file mode 100644
index 0000000000..4bedb5e0ea
--- /dev/null
+++ b/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 AttendedDbContexts { get; }
+
+ public EfCoreTransactionApi(IDbContextTransaction dbContextTransaction, DbContext starterDbContext)
+ {
+ DbContextTransaction = dbContextTransaction;
+ StarterDbContext = starterDbContext;
+ AttendedDbContexts = new List();
+ }
+
+ 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs
new file mode 100644
index 0000000000..881b8d202b
--- /dev/null
+++ b/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 AttendedDbContexts { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs
new file mode 100644
index 0000000000..9536e4cad8
--- /dev/null
+++ b/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(IUnitOfWork unitOfWork, DbContextCreationContext creationContext)
+ where TDbContext : DbContext;
+ }
+}
diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs
index 717a68ebe7..b4f573b26d 100644
--- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs
+++ b/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 : IDbContextProvider
where TDbContext : AbpDbContext
{
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)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 dbContext;
+
+ if (unitOfWork.Options.IsTransactional == true)
+ {
+ dbContext = _transactionStrategy.CreateDbContext(unitOfWork, creationContext);
+ }
+ else
+ {
+ dbContext = unitOfWork.ServiceProvider.GetRequiredService();
+ }
+
+ if (unitOfWork.Options.Timeout.HasValue &&
+ dbContext.Database.IsRelational() &&
+ !dbContext.Database.GetCommandTimeout().HasValue)
+ {
+ dbContext.Database.SetCommandTimeout(unitOfWork.Options.Timeout.Value.TotalSeconds.To());
+ }
+
+ return dbContext;
}
}
}
diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs
index 15e03332f6..8e848aae34 100644
--- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs
+++ b/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()
{
diff --git a/src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs
new file mode 100644
index 0000000000..1f6f9aa226
--- /dev/null
+++ b/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();
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs
new file mode 100644
index 0000000000..dde7ace71f
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs
index 442c03c075..49352a3bd2 100644
--- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs
+++ b/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; }
diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs
index 2409fbd4ba..e0e6beaf02 100644
--- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs
+++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs
@@ -35,6 +35,7 @@ namespace Volo.Abp.Uow
public IServiceProvider ServiceProvider { get; }
private readonly Dictionary _databaseApis;
+ private readonly Dictionary _transactionApis;
private Exception _exception;
private bool _isCompleted;
@@ -45,6 +46,7 @@ namespace Volo.Abp.Uow
ServiceProvider = serviceProvider;
_databaseApis = new Dictionary();
+ _transactionApis = new Dictionary();
}
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();
+ }
+ }
}
}
\ No newline at end of file