diff --git a/src/ShardingCore/DbContexts/ShardingDbContextFactory.cs b/src/ShardingCore/DbContexts/ShardingDbContextFactory.cs index de95b2e5..940de0e7 100644 --- a/src/ShardingCore/DbContexts/ShardingDbContextFactory.cs +++ b/src/ShardingCore/DbContexts/ShardingDbContextFactory.cs @@ -34,35 +34,25 @@ namespace ShardingCore.DbContexts } public DbContext Create(ShardingDbContextOptions shardingDbContextOptions) { + var tail=shardingDbContextOptions.Tail; var shardingConfigEntry = _shardingCoreOptions.GetShardingConfig(); - using (var scope = _shardingTableScopeFactory.CreateScope()) + var dbContext = shardingConfigEntry.Creator(shardingDbContextOptions); + if (!string.IsNullOrWhiteSpace(tail) && dbContext is IShardingTableDbContext shardingTableDbContext) { - string tail = null; - string modelChangeKey = null; - if (!string.IsNullOrWhiteSpace(shardingDbContextOptions.Tail)) - { - tail = shardingDbContextOptions.Tail; - modelChangeKey = $"sharding_{tail}"; - } - scope.ShardingTableAccessor.Context = ShardingTableContext.Create(tail); - var dbContext= shardingConfigEntry.Creator(shardingDbContextOptions); - if (modelChangeKey != null&& dbContext is IShardingTableDbContext shardingTableDbContext) - { - shardingTableDbContext.ModelChangeKey = modelChangeKey; - } - - var filters = _dbContextCreateFilterManager.GetFilters(); - if (filters.Any()) - { - foreach (var dbContextCreateFilter in filters) - { - dbContextCreateFilter.CreateAfter(dbContext); - } - } - var dbContextModel = dbContext.Model; - return dbContext; + shardingTableDbContext.SetShardingTableDbContextTail(tail); } + + var filters = _dbContextCreateFilterManager.GetFilters(); + if (filters.Any()) + { + foreach (var dbContextCreateFilter in filters) + { + dbContextCreateFilter.CreateAfter(dbContext); + } + } + var dbContextModel = dbContext.Model; + return dbContext; } public DbContext Create(DbConnection dbConnection,string tail) diff --git a/src/ShardingCore/DbContexts/ShardingDbContexts/IShardingTableAccessor.cs b/src/ShardingCore/DbContexts/ShardingDbContexts/IShardingTableAccessor.cs deleted file mode 100644 index d287b622..00000000 --- a/src/ShardingCore/DbContexts/ShardingDbContexts/IShardingTableAccessor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace ShardingCore.DbContexts.ShardingDbContexts -{ - /* - * @Author: xjm - * @Description: - * @Date: 2021/03/09 00:00:00 - * @Ver: 1.0 - * @Email: 326308290@qq.com - */ - /// - /// - /// - public interface IShardingTableAccessor - { - ShardingTableContext Context { get; set; } - } -} \ No newline at end of file diff --git a/src/ShardingCore/DbContexts/ShardingDbContexts/IShardingTableScopeFactory.cs b/src/ShardingCore/DbContexts/ShardingDbContexts/IShardingTableScopeFactory.cs deleted file mode 100644 index 51f6edae..00000000 --- a/src/ShardingCore/DbContexts/ShardingDbContexts/IShardingTableScopeFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace ShardingCore.DbContexts.ShardingDbContexts -{ - /* - * @Author: xjm - * @Description: - * @Date: 2021/03/09 00:00:00 - * @Ver: 1.0 - * @Email: 326308290@qq.com - */ - /// - /// - /// - public interface IShardingTableScopeFactory - { - /// - /// 创建查询scope - /// - /// - ShardingTableScope CreateScope(); - } -} \ No newline at end of file diff --git a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableAccessor.cs b/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableAccessor.cs deleted file mode 100644 index 72919a47..00000000 --- a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableAccessor.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading; -using ShardingCore.Core.VirtualTables; - -namespace ShardingCore.DbContexts.ShardingDbContexts -{ - /* - * @Author: xjm - * @Description: - * @Date: 2021/03/09 13:08:15 - * @Ver: 1.0 - * @Email: 326308290@qq.com - */ - - /// - /// - /// - public class ShardingTableAccessor:IShardingTableAccessor - { - private static AsyncLocal _context = new AsyncLocal(); - - /// - public ShardingTableContext Context - { - get => _context.Value; - set => _context.Value = value; - } - } -} \ No newline at end of file diff --git a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableContext.cs b/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableContext.cs deleted file mode 100644 index f1f42fa5..00000000 --- a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableContext.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace ShardingCore.DbContexts.ShardingDbContexts -{ - /* - * @Author: xjm - * @Description: - * @Date: 2021/03/09 13:04:52 - * @Ver: 1.0 - * @Email: 326308290@qq.com - */ - - /// - /// 分表上下文 - /// - public class ShardingTableContext - { - private ShardingTableContext(string tail) - { - Tail = tail; - } - public static ShardingTableContext Create(string tail) - { - return new ShardingTableContext(tail); - } - /// - /// 尾巴 - /// - public string Tail { get; set; } - } -} \ No newline at end of file diff --git a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableScope.cs b/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableScope.cs deleted file mode 100644 index cf04fa67..00000000 --- a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableScope.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace ShardingCore.DbContexts.ShardingDbContexts -{ - /* - * @Author: xjm - * @Description: - * @Date: 2021/03/09 00:00:00 - * @Ver: 1.0 - * @Email: 326308290@qq.com - */ - /// - /// - /// - public class ShardingTableScope: IDisposable - { - - /// - /// 分表配置访问器 - /// - public IShardingTableAccessor ShardingTableAccessor { get; } - - /// - /// 构造函数 - /// - /// - public ShardingTableScope(IShardingTableAccessor shardingAccessor) - { - ShardingTableAccessor = shardingAccessor; - } - - /// - /// 回收 - /// - public void Dispose() - { - ShardingTableAccessor.Context = null; - } - } -} \ No newline at end of file diff --git a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableScopeFactory.cs b/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableScopeFactory.cs deleted file mode 100644 index 0e61c0a2..00000000 --- a/src/ShardingCore/DbContexts/ShardingDbContexts/ShardingTableScopeFactory.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace ShardingCore.DbContexts.ShardingDbContexts -{ - /* - * @Author: xjm - * @Description: - * @Date: 2021/03/09 13:13:58 - * @Ver: 1.0 - * @Email: 326308290@qq.com - */ - - /// - /// - /// - public class ShardingTableScopeFactory:IShardingTableScopeFactory - { - private readonly IShardingTableAccessor _shardingTableAccessor; - - /// - /// 构造函数 - /// - /// - public ShardingTableScopeFactory(IShardingTableAccessor shardingTableAccessor) - { - _shardingTableAccessor = shardingTableAccessor; - } - /// - /// 创建环境 - /// - /// - public ShardingTableScope CreateScope() - { - _shardingTableAccessor.Context = null; - return new ShardingTableScope(_shardingTableAccessor); - } - } -} \ No newline at end of file diff --git a/src/ShardingCore/Extensions/ShardingExtension.cs b/src/ShardingCore/Extensions/ShardingExtension.cs new file mode 100644 index 00000000..798056a2 --- /dev/null +++ b/src/ShardingCore/Extensions/ShardingExtension.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ShardingCore.Exceptions; +using ShardingCore.Sharding.Abstractions; + +namespace ShardingCore.Extensions +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/8/15 16:12:27 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + /// + /// + /// + public static class ShardingExtension + { + private static readonly string ShardingTableDbContextFormat = $"sharding_{Guid.NewGuid():n}_"; + /// + /// 获取分表的tail + /// + /// + /// + public static string GetShardingTableDbContextTail(this IShardingTableDbContext dbContext) + { + if (string.IsNullOrWhiteSpace(dbContext.ModelChangeKey)) + throw new ShardingCoreException($"cant found ModelChangeKey in {dbContext.GetType().FullName}"); + return dbContext.ModelChangeKey.Replace(ShardingTableDbContextFormat, string.Empty); + + } + /// + /// 设置分表的tail + /// + /// + /// + public static void SetShardingTableDbContextTail(this IShardingTableDbContext dbContext, string tail) + { + if (!string.IsNullOrWhiteSpace(dbContext.ModelChangeKey)) + throw new ShardingCoreException($"repeat set ModelChangeKey in {dbContext.GetType().FullName}"); + dbContext.ModelChangeKey = $"{ShardingTableDbContextFormat}{tail}"; + } + } +} diff --git a/src/ShardingCore/Sharding/AbstractShardingDbContext.cs b/src/ShardingCore/Sharding/AbstractShardingDbContext.cs new file mode 100644 index 00000000..a09de2da --- /dev/null +++ b/src/ShardingCore/Sharding/AbstractShardingDbContext.cs @@ -0,0 +1,470 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using ShardingCore.Core; +using ShardingCore.Core.VirtualRoutes.TableRoutes; +using ShardingCore.Core.VirtualTables; +using ShardingCore.DbContexts; +using ShardingCore.Extensions; +using ShardingCore.Sharding.Abstractions; + +namespace ShardingCore.Sharding +{ + /* + * @Author: xjm + * @Description: + * @Date: Saturday, 14 August 2021 09:57:08 + * @Email: 326308290@qq.com + */ + /// + /// 分表分库的dbcontext + /// + /// + public abstract class AbstractShardingDbContext : DbContext, IShardingDbContext where T : DbContext + { + private readonly string EMPTY_SHARDING_TAIL_ID = Guid.NewGuid().ToString("n"); + private readonly ConcurrentDictionary _dbContextCaches = new ConcurrentDictionary(); + private readonly IVirtualTableManager _virtualTableManager; + private readonly IShardingDbContextFactory _shardingDbContextFactory; + public AbstractShardingDbContext(DbContextOptions options) : base(options) + { + _shardingDbContextFactory = ShardingContainer.GetService(); + } + public DbContext GetDbContext(bool track, string tail) + { + if (!_dbContextCaches.TryGetValue(tail, out var dbContext)) + { + dbContext = _shardingDbContextFactory.Create(track ? this.Database.GetDbConnection() : null, tail == EMPTY_SHARDING_TAIL_ID ? string.Empty : tail); ; + _dbContextCaches.TryAdd(tail, dbContext); + } + + //if (IsOpenTransaction) + //{ + // _dbTransaction.Use(dbContext); + //} + + return dbContext; + } + + public bool IsBeginTransaction => Database.CurrentTransaction != null; + public DbContext CreateGenericDbContext(T entity) where T : class + { + var tail = EMPTY_SHARDING_TAIL_ID; + if (entity.IsShardingTable()) + { + var physicTable = _virtualTableManager.GetVirtualTable(entity.GetType()).RouteTo(new TableRouteConfig(null, entity as IShardingTable, null))[0]; + tail = physicTable.Tail; + } + + return GetDbContext(true, tail); + } + + public override EntityEntry Add(object entity) + { + return CreateGenericDbContext(entity).Add(entity); + } + + public override EntityEntry Add(TEntity entity) + { + return CreateGenericDbContext(entity).Add(entity); + } + + public override ValueTask> AddAsync(TEntity entity, CancellationToken cancellationToken = new CancellationToken()) + { + return CreateGenericDbContext(entity).AddAsync(entity, cancellationToken); + } + + public override ValueTask AddAsync(object entity, CancellationToken cancellationToken = new CancellationToken()) + { + return CreateGenericDbContext(entity).AddAsync(entity, cancellationToken); + } + + public override void AddRange(params object[] entities) + { + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.AddRange(group.Select(o => o.Entity)); + } + } + + public override void AddRange(IEnumerable entities) + { + + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.AddRange(group.Select(o => o.Entity)); + } + } + + public override async Task AddRangeAsync(params object[] entities) + { + + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + await group.Key.AddRangeAsync(group.Select(o => o.Entity)); + } + } + + public override async Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = new CancellationToken()) + { + + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + await group.Key.AddRangeAsync(group.Select(o => o.Entity)); + } + } + + public override EntityEntry Attach(TEntity entity) + { + return CreateGenericDbContext(entity).Attach(entity); + } + + public override EntityEntry Attach(object entity) + { + return CreateGenericDbContext(entity).Attach(entity); + } + + public override void AttachRange(params object[] entities) + { + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.AttachRange(group.Select(o => o.Entity)); + } + } + + public override void AttachRange(IEnumerable entities) + { + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.AttachRange(group.Select(o => o.Entity)); + } + } + + + public override DatabaseFacade Database => _dbContextCaches.Any() + ? _dbContextCaches.First().Value.Database + : GetDbContext(true, string.Empty).Database; + + public override EntityEntry Entry(TEntity entity) + { + return CreateGenericDbContext(entity).Entry(entity); + } + + public override EntityEntry Entry(object entity) + { + return CreateGenericDbContext(entity).Entry(entity); + } + + public override EntityEntry Update(TEntity entity) + { + return CreateGenericDbContext(entity).Update(entity); + } + + public override EntityEntry Update(object entity) + { + return CreateGenericDbContext(entity).Update(entity); + } + + public override void UpdateRange(params object[] entities) + { + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.UpdateRange(group.Select(o => o.Entity)); + } + } + + public override void UpdateRange(IEnumerable entities) + { + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.UpdateRange(group.Select(o => o.Entity)); + } + } + + public override EntityEntry Remove(TEntity entity) + { + return CreateGenericDbContext(entity).Remove(entity); + } + + public override EntityEntry Remove(object entity) + { + return CreateGenericDbContext(entity).Remove(entity); + } + + public override void RemoveRange(params object[] entities) + { + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.RemoveRange(group.Select(o => o.Entity)); + } + } + + public override void RemoveRange(IEnumerable entities) + { + var groups = entities.Select(o => + { + var dbContext = CreateGenericDbContext(o); + return new + { + DbContext = dbContext, + Entity = o + }; + }).GroupBy(g => g.DbContext); + + foreach (var group in groups) + { + group.Key.RemoveRange(group.Select(o => o.Entity)); + } + } + + public override int SaveChanges() + { + var isBeginTransaction = IsBeginTransaction; + //如果是内部开的事务就内部自己消化 + if (!isBeginTransaction) + { + Database.BeginTransaction(); + } + int i = 0; + + try + { + + foreach (var dbContextCache in _dbContextCaches) + { + dbContextCache.Value.Database.UseTransaction(Database.CurrentTransaction.GetDbTransaction()); + i += dbContextCache.Value.SaveChanges(); + } + if (!isBeginTransaction) + Database.CurrentTransaction.Commit(); + } + finally + { + if (!isBeginTransaction) + Database.CurrentTransaction.Dispose(); + } + return i; + } + + public override int SaveChanges(bool acceptAllChangesOnSuccess) + { + + var isBeginTransaction = IsBeginTransaction; + //如果是内部开的事务就内部自己消化 + if (!isBeginTransaction) + { + Database.BeginTransaction(); + } + int i = 0; + + try + { + + foreach (var dbContextCache in _dbContextCaches) + { + dbContextCache.Value.Database.UseTransaction(Database.CurrentTransaction.GetDbTransaction()); + i += dbContextCache.Value.SaveChanges(acceptAllChangesOnSuccess); + } + if (!isBeginTransaction) + Database.CurrentTransaction.Commit(); + } + finally + { + if (!isBeginTransaction) + Database.CurrentTransaction.Dispose(); + } + return i; + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) + { + + var isBeginTransaction = IsBeginTransaction; + //如果是内部开的事务就内部自己消化 + if (!isBeginTransaction) + { + await Database.BeginTransactionAsync(cancellationToken); + } + int i = 0; + + try + { + + foreach (var dbContextCache in _dbContextCaches) + { + await dbContextCache.Value.Database.UseTransactionAsync(Database.CurrentTransaction.GetDbTransaction(), cancellationToken: cancellationToken); + i +=await dbContextCache.Value.SaveChangesAsync(cancellationToken); + } + if (!isBeginTransaction) + await Database.CurrentTransaction.CommitAsync(cancellationToken); + } + finally + { + if (!isBeginTransaction) + await Database.CurrentTransaction.DisposeAsync(); + } + return i; + } + + public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken()) + { + + var isBeginTransaction = IsBeginTransaction; + //如果是内部开的事务就内部自己消化 + if (!isBeginTransaction) + { + await Database.BeginTransactionAsync(cancellationToken); + } + int i = 0; + + try + { + + foreach (var dbContextCache in _dbContextCaches) + { + await dbContextCache.Value.Database.UseTransactionAsync(Database.CurrentTransaction.GetDbTransaction(), cancellationToken: cancellationToken); + i += await dbContextCache.Value.SaveChangesAsync(acceptAllChangesOnSuccess,cancellationToken); + } + if (!isBeginTransaction) + await Database.CurrentTransaction.CommitAsync(cancellationToken); + } + finally + { + if (!isBeginTransaction) + await Database.CurrentTransaction.DisposeAsync(); + } + return i; + } + + public override void Dispose() + { + foreach (var dbContextCache in _dbContextCaches) + { + try + { + dbContextCache.Value.Dispose(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + base.Dispose(); + } + + public override async ValueTask DisposeAsync() + { + foreach (var dbContextCache in _dbContextCaches) + { + try + { + await dbContextCache.Value.DisposeAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + await base.DisposeAsync(); + } + + } +} \ No newline at end of file diff --git a/src/ShardingCore/Sharding/ShardingDbContext.cs b/src/ShardingCore/Sharding/ShardingDbContext.cs deleted file mode 100644 index de4e915c..00000000 --- a/src/ShardingCore/Sharding/ShardingDbContext.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Concurrent; -using Microsoft.EntityFrameworkCore; -using ShardingCore.Core; -using ShardingCore.Core.VirtualRoutes.TableRoutes; -using ShardingCore.Core.VirtualTables; -using ShardingCore.DbContexts; -using ShardingCore.Extensions; -using ShardingCore.Sharding.Abstractions; - -namespace ShardingCore.Sharding -{ - /* - * @Author: xjm - * @Description: - * @Date: Saturday, 14 August 2021 09:57:08 - * @Email: 326308290@qq.com - */ - /// - /// 分表分库的dbcontext - /// - /// - public abstract class ShardingDbContext : DbContext, IShardingDbContext where T : DbContext - { - private readonly string EMPTY_SHARDING_TAIL_ID = Guid.NewGuid().ToString("n"); - private readonly ConcurrentDictionary _dbContextCaches = new ConcurrentDictionary(); - private readonly IVirtualTableManager _virtualTableManager; - private readonly IShardingDbContextFactory _shardingDbContextFactory; - public ShardingDbContext(DbContextOptions options):base(options) - { - _shardingDbContextFactory = ShardingContainer.GetService(); - } - public DbContext GetDbContext(bool track, string tail) - { - if (!_dbContextCaches.TryGetValue(tail, out var dbContext)) - { - dbContext = _shardingDbContextFactory.Create(track ? this.Database.GetDbConnection() : null, tail == EMPTY_SHARDING_TAIL_ID ? string.Empty : tail); ; - _dbContextCaches.TryAdd(tail, dbContext); - } - - //if (IsOpenTransaction) - //{ - // _dbTransaction.Use(dbContext); - //} - - return dbContext; - } - public DbContext CreateGenericDbContext(T entity) where T : class - { - var tail = EMPTY_SHARDING_TAIL_ID; - if (entity.IsShardingTable()) - { - var physicTable = _virtualTableManager.GetVirtualTable(entity.GetType()).RouteTo(new TableRouteConfig(null, entity as IShardingTable, null))[0]; - tail = physicTable.Tail; - } - - return GetDbContext(true,tail); - } - - } -} \ No newline at end of file diff --git a/test/ShardingCore.Test50/ShardingDefaultDbContext.cs b/test/ShardingCore.Test50/ShardingDefaultDbContext.cs index c1ec300e..14fe4cd2 100644 --- a/test/ShardingCore.Test50/ShardingDefaultDbContext.cs +++ b/test/ShardingCore.Test50/ShardingDefaultDbContext.cs @@ -16,7 +16,7 @@ namespace ShardingCore.Test50 * @Ver: 1.0 * @Email: 326308290@qq.com */ - public class ShardingDefaultDbContext:ShardingDbContext + public class ShardingDefaultDbContext:AbstractShardingDbContext { public ShardingDefaultDbContext(DbContextOptions options) : base(options) {