Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EfCoreTransactionStrategy to share transactions #1834

Merged
merged 5 commits into from Mar 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using System.Data.Common;

namespace Abp.EntityFrameworkCore.Configuration
{
Expand All @@ -7,12 +8,20 @@ public class AbpDbContextConfiguration<TDbContext>
{
public string ConnectionString {get; internal set; }

public DbConnection Connection { get; internal set; }

public DbContextOptionsBuilder<TDbContext> DbContextOptions { get; }

public AbpDbContextConfiguration(string connectionString)
{
ConnectionString = connectionString;
DbContextOptions = new DbContextOptionsBuilder<TDbContext>();
}

public AbpDbContextConfiguration(DbConnection connection)
{
Connection = connection;
DbContextOptions = new DbContextOptionsBuilder<TDbContext>();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using System;
using System.Reflection;
using Abp.Dependency;
using Abp.EntityFramework;
using Abp.EntityFrameworkCore.Configuration;
using Microsoft.EntityFrameworkCore;
using System;
using System.Data.Common;
using System.Reflection;

namespace Abp.EntityFrameworkCore
{
public class DefaultDbContextResolver : IDbContextResolver, ITransientDependency
{
private static readonly MethodInfo CreateOptionsMethod = typeof(DefaultDbContextResolver).GetMethod("CreateOptions", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly MethodInfo CreateOptionsFromConnectionMethod = typeof(DefaultDbContextResolver).GetMethod("CreateOptionsFromConnection", BindingFlags.NonPublic | BindingFlags.Instance);

private readonly IIocResolver _iocResolver;
private readonly IDbContextTypeMatcher _dbContextTypeMatcher;
Expand Down Expand Up @@ -43,11 +45,37 @@ public TDbContext Resolve<TDbContext>(string connectionString)
});
}

public TDbContext Resolve<TDbContext>(DbConnection existingConnection)
where TDbContext : DbContext
{
var dbContextType = typeof(TDbContext);

if (!dbContextType.IsAbstract)
{
return _iocResolver.Resolve<TDbContext>(new
{
options = CreateOptionsFromConnection<TDbContext>(existingConnection)
});
}

var concreteType = _dbContextTypeMatcher.GetConcreteType(dbContextType);

return (TDbContext)_iocResolver.Resolve(concreteType, new
{
options = CreateOptionsForType(concreteType, existingConnection)
});
}

private object CreateOptionsForType(Type dbContextType, string connectionString)
{
return CreateOptionsMethod.MakeGenericMethod(dbContextType).Invoke(this, new object[] { connectionString });
}

private object CreateOptionsForType(Type dbContextType, DbConnection connection)
{
return CreateOptionsFromConnectionMethod.MakeGenericMethod(dbContextType).Invoke(this, new object[] { connection });
}

protected virtual DbContextOptions<TDbContext> CreateOptions<TDbContext>(string connectionString)
where TDbContext : DbContext
{
Expand All @@ -70,5 +98,28 @@ protected virtual DbContextOptions<TDbContext> CreateOptions<TDbContext>(string

throw new AbpException($"Could not resolve DbContextOptions for {typeof(TDbContext).AssemblyQualifiedName}.");
}

protected virtual DbContextOptions<TDbContext> CreateOptionsFromConnection<TDbContext>(DbConnection existingConnection)
where TDbContext : DbContext
{
if (_iocResolver.IsRegistered<IAbpDbContextConfigurer<TDbContext>>())
{
var configuration = new AbpDbContextConfiguration<TDbContext>(existingConnection);

using (var configurer = _iocResolver.ResolveAsDisposable<IAbpDbContextConfigurer<TDbContext>>())
{
configurer.Object.Configure(configuration);
}

return configuration.DbContextOptions.Options;
}

if (_iocResolver.IsRegistered<DbContextOptions<TDbContext>>())
{
return _iocResolver.Resolve<DbContextOptions<TDbContext>>();
}

throw new AbpException($"Could not resolve DbContextOptions for {typeof(TDbContext).AssemblyQualifiedName}.");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using Microsoft.EntityFrameworkCore;
using System.Data.Common;

namespace Abp.EntityFrameworkCore
{
public interface IDbContextResolver
{
TDbContext Resolve<TDbContext>(string connectionString)
where TDbContext : DbContext;
}

TDbContext Resolve<TDbContext>(DbConnection existingConnection)
where TDbContext : DbContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System.Collections.Generic;

namespace Abp.EntityFrameworkCore.Uow
{
public class ActiveTransactionInfo
{
public IDbContextTransaction DbContextTransaction { get; }

public DbContext StarterDbContext { get; }

public List<DbContext> AttendedDbContexts { get; }

public ActiveTransactionInfo(IDbContextTransaction dbContextTransaction, DbContext starterDbContext)
{
DbContextTransaction = dbContextTransaction;
StarterDbContext = starterDbContext;

AttendedDbContexts = new List<DbContext>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using Abp.Collections.Extensions;
using Abp.Dependency;
using Abp.Domain.Uow;
using Abp.EntityFrameworkCore.Extensions;
using Abp.Transactions.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System.Collections.Generic;
using System.Transactions;

namespace Abp.EntityFrameworkCore.Uow
{
public class DbContextEfCoreTransactionStrategy : IEfCoreTransactionStrategy, ITransientDependency
{
protected UnitOfWorkOptions Options { get; private set; }

protected IDictionary<string, ActiveTransactionInfo> ActiveTransactions { get; }

public DbContextEfCoreTransactionStrategy()
{
ActiveTransactions = new Dictionary<string, ActiveTransactionInfo>();
}

public void Commit()
{
foreach (var activeTransaction in ActiveTransactions.Values)
{
activeTransaction.DbContextTransaction.Commit();

foreach (var dbContext in activeTransaction.AttendedDbContexts)
{
if (dbContext.HasRelationalTransactionManager())
{
//Relational databases use the SharedTransaction
continue;
}

dbContext.Database.CommitTransaction();
}
}
}

public DbContext CreateDbContext<TDbContext>(string connectionString, IDbContextResolver dbContextResolver) where TDbContext : DbContext
{
DbContext dbContext;

var activeTransaction = ActiveTransactions.GetOrDefault(connectionString);

if (activeTransaction == null)
{
dbContext = dbContextResolver.Resolve<TDbContext>(connectionString);
}
else
{
dbContext = dbContextResolver.Resolve<TDbContext>(activeTransaction.DbContextTransaction.GetDbTransaction().Connection);
}

if (dbContext.HasRelationalTransactionManager())
{
if (activeTransaction == null)
{
var dbtransaction = dbContext.Database.BeginTransaction((Options.IsolationLevel ?? IsolationLevel.ReadUncommitted).ToSystemDataIsolationLevel());
activeTransaction = new ActiveTransactionInfo(dbtransaction, dbContext);
ActiveTransactions[connectionString] = activeTransaction;
}
else
{
dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction());
activeTransaction.AttendedDbContexts.Add(dbContext);
}
}
else
{
dbContext.Database.BeginTransaction();
}

return dbContext;
}

public void Dispose(IIocResolver iocResolver)
{
foreach (var activeTransaction in ActiveTransactions.Values)
{
activeTransaction.DbContextTransaction.Dispose();

foreach (var attendedDbContext in activeTransaction.AttendedDbContexts)
{
iocResolver.Release(attendedDbContext);
}

activeTransaction.StarterDbContext.Dispose();
}

ActiveTransactions.Clear();
}

public void InitOptions(UnitOfWorkOptions options)
{
Options = options;
}
}
}