├── README.md ├── ShardingCoreMultiTenantSys ├── IdentitySys │ ├── OrderShardingTypeEnum.cs │ ├── DbTypeEnum.cs │ ├── ShardingConfigs │ │ ├── IShardingBuilder.cs │ │ ├── ShardingTenantOptions.cs │ │ └── DefaultShardingBuilder.cs │ ├── Domain │ │ ├── Entities │ │ │ ├── SysUser.cs │ │ │ └── SysUserTenantConfig.cs │ │ └── Maps │ │ │ ├── SysUserMap.cs │ │ │ └── SysUserTenantConfigMap.cs │ └── IdentityDbContext.cs ├── appsettings.Development.json ├── MigrationsAssemblies │ ├── IMigrationNamespace.cs │ ├── MySqlMigrationNamespace.cs │ ├── SqlServerMigrationNamespace.cs │ ├── MigrationDesignTimeServices.cs │ ├── MyMigrationsScaffolder.cs │ ├── MigrationNamespaceExtension.cs │ ├── MultiDatabaseMigrationsAssembly.cs │ └── SharedTypeExtensions.cs ├── appsettings.json ├── Tenants │ ├── ITenantContextAccessor.cs │ ├── TenantContextAccessor.cs │ ├── TenantScope.cs │ ├── TenantContext.cs │ ├── ITenantManager.cs │ └── DefaultTenantManager.cs ├── TenantSys │ ├── Domain │ │ ├── Entities │ │ │ └── Order.cs │ │ └── Maps │ │ │ └── OrderMap.cs │ └── Shardings │ │ ├── OrderModTableRoute.cs │ │ ├── OrderMonthTableRoute.cs │ │ ├── ShardingSqlServerMigrationsSqlGenerator.cs │ │ └── ShardingMySqlMigrationsSqlGenerator.cs ├── WeatherForecast.cs ├── DbContexts │ └── TenantDbContext.cs ├── Properties │ └── launchSettings.json ├── Extensions │ ├── MigrationExtension.cs │ └── TenantExtension.cs ├── Controllers │ ├── WeatherForecastController.cs │ ├── TenantSys │ │ └── TenantController.cs │ └── IdentitySys │ │ └── PassportController.cs ├── ShardingCoreMultiTenantSys.csproj ├── Migrations │ ├── SqlServer │ │ ├── 20220724043120_InitialCreate.cs │ │ ├── TenantDbContextModelSnapshot.cs │ │ └── 20220724043120_InitialCreate.Designer.cs │ ├── MySql │ │ ├── 20220724043347_InitialCreate.cs │ │ ├── TenantDbContextModelSnapshot.cs │ │ └── 20220724043347_InitialCreate.Designer.cs │ └── Identity │ │ ├── 20220724042931_IdentityInit.cs │ │ ├── IdentityDbContextModelSnapshot.cs │ │ └── 20220724042931_IdentityInit.Designer.cs ├── Middlewares │ └── TenantSelectMiddleware.cs └── Program.cs ├── ShardingCoreMultiTenantSys.sln └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | # ShardingCoreMultiTenantSys 2 | ShardingCoreMultiTenantSys 3 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/OrderShardingTypeEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.IdentitySys; 2 | 3 | public enum OrderShardingTypeEnum 4 | { 5 | Mod=1, 6 | ByMonth=2 7 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/DbTypeEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.IdentitySys 2 | { 3 | public enum DbTypeEnum 4 | { 5 | MSSQL = 1, 6 | MYSQL = 2 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/IMigrationNamespace.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.MigrationsAssemblies; 2 | 3 | public interface IMigrationNamespace 4 | { 5 | string GetNamespace(); 6 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Tenants/ITenantContextAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.Tenants 2 | { 3 | public interface ITenantContextAccessor 4 | { 5 | TenantContext? TenantContext { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/ShardingConfigs/IShardingBuilder.cs: -------------------------------------------------------------------------------- 1 | using ShardingCore.Core.RuntimeContexts; 2 | 3 | namespace ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs; 4 | 5 | public interface IShardingBuilder 6 | { 7 | IShardingRuntimeContext Build(ShardingTenantOptions tenantOptions); 8 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/MySqlMigrationNamespace.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.MigrationsAssemblies; 2 | 3 | public class MySqlMigrationNamespace:IMigrationNamespace 4 | { 5 | public string GetNamespace() 6 | { 7 | return "ShardingCoreMultiTenantSys.Migrations.MySql"; 8 | } 9 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/SqlServerMigrationNamespace.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.MigrationsAssemblies; 2 | 3 | public class SqlServerMigrationNamespace:IMigrationNamespace 4 | { 5 | public string GetNamespace() 6 | { 7 | return "ShardingCoreMultiTenantSys.Migrations.SqlServer"; 8 | } 9 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/TenantSys/Domain/Entities/Order.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.TenantSys.Domain.Entities 2 | { 3 | public class Order 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public DateTime CreationTime { get; set; } 8 | public bool IsDeleted { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys 2 | { 3 | public class WeatherForecast 4 | { 5 | public DateTime Date { get; set; } 6 | 7 | public int TemperatureC { get; set; } 8 | 9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 10 | 11 | public string? Summary { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/Domain/Entities/SysUser.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities 2 | { 3 | public class SysUser 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public string Password { get; set; } 8 | public DateTime CreationTime { get; set; } 9 | public bool IsDeleted { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Tenants/TenantContextAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.Tenants 2 | { 3 | 4 | public class TenantContextAccessor:ITenantContextAccessor 5 | { 6 | private static readonly AsyncLocal _tenantContext = new AsyncLocal(); 7 | public TenantContext? TenantContext 8 | { 9 | get => _tenantContext.Value; 10 | set => _tenantContext.Value = value; 11 | } 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/Domain/Entities/SysUserTenantConfig.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities 2 | { 3 | public class SysUserTenantConfig 4 | { 5 | public string Id { get; set; } 6 | public string UserId { get; set; } 7 | /// 8 | /// 添加ShardingCore配置的Json包 9 | /// 10 | public string ConfigJson { get; set; } 11 | public DateTime CreationTime { get; set; } 12 | public bool IsDeleted { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Tenants/TenantScope.cs: -------------------------------------------------------------------------------- 1 | namespace ShardingCoreMultiTenantSys.Tenants 2 | { 3 | public class TenantScope:IDisposable 4 | { 5 | public TenantScope(ITenantContextAccessor tenantContextAccessor) 6 | { 7 | TenantContextAccessor = tenantContextAccessor; 8 | } 9 | 10 | public ITenantContextAccessor TenantContextAccessor { get; } 11 | 12 | public void Dispose() 13 | { 14 | TenantContextAccessor.TenantContext = null; 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Tenants/TenantContext.cs: -------------------------------------------------------------------------------- 1 | using ShardingCore.Core.RuntimeContexts; 2 | 3 | namespace ShardingCoreMultiTenantSys.Tenants 4 | { 5 | public class TenantContext 6 | { 7 | private readonly IShardingRuntimeContext _shardingRuntimeContext; 8 | 9 | public TenantContext(IShardingRuntimeContext shardingRuntimeContext) 10 | { 11 | _shardingRuntimeContext = shardingRuntimeContext; 12 | } 13 | public IShardingRuntimeContext GetShardingRuntimeContext() 14 | { 15 | return _shardingRuntimeContext; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/MigrationDesignTimeServices.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Design; 2 | using Microsoft.EntityFrameworkCore.Migrations.Design; 3 | 4 | namespace ShardingCoreMultiTenantSys.MigrationsAssemblies; 5 | 6 | /// 7 | /// https://docs.microsoft.com/en-us/ef/core/cli/services 8 | /// 9 | public class MigrationDesignTimeServices: IDesignTimeServices 10 | { 11 | public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) 12 | { 13 | serviceCollection.AddSingleton(); 14 | } 15 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/IdentityDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using ShardingCoreMultiTenantSys.IdentitySys.Domain.Maps; 3 | 4 | namespace ShardingCoreMultiTenantSys.IdentitySys 5 | { 6 | public class IdentityDbContext:DbContext 7 | { 8 | public IdentityDbContext(DbContextOptions options):base(options) 9 | { 10 | 11 | } 12 | 13 | protected override void OnModelCreating(ModelBuilder modelBuilder) 14 | { 15 | base.OnModelCreating(modelBuilder); 16 | modelBuilder.ApplyConfiguration(new SysUserMap()); 17 | modelBuilder.ApplyConfiguration(new SysUserTenantConfigMap()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/TenantSys/Domain/Maps/OrderMap.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using ShardingCoreMultiTenantSys.TenantSys.Domain.Entities; 4 | 5 | namespace ShardingCoreMultiTenantSys.TenantSys.Domain.Maps 6 | { 7 | public class OrderMap:IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(o => o.Id); 12 | builder.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50); 13 | builder.Property(o => o.Name).IsRequired().HasMaxLength(100); 14 | builder.HasQueryFilter(o => o.IsDeleted == false); 15 | builder.ToTable(nameof(Order)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/TenantSys/Shardings/OrderModTableRoute.cs: -------------------------------------------------------------------------------- 1 | using ShardingCore.Core.EntityMetadatas; 2 | using ShardingCore.VirtualRoutes.Mods; 3 | using ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs; 4 | using ShardingCoreMultiTenantSys.TenantSys.Domain.Entities; 5 | 6 | namespace ShardingCoreMultiTenantSys.TenantSys.Shardings; 7 | 8 | public class OrderModTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRoute 9 | { 10 | private readonly ShardingTenantOptions _shardingTenantOptions; 11 | 12 | public OrderModTableRoute(ShardingTenantOptions shardingTenantOptions) : base(2, 5) 13 | { 14 | _shardingTenantOptions = shardingTenantOptions; 15 | } 16 | 17 | public override void Configure(EntityMetadataTableBuilder builder) 18 | { 19 | builder.ShardingProperty(o => o.Id); 20 | } 21 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/DbContexts/TenantDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using ShardingCore.Core.VirtualRoutes.TableRoutes.RouteTails.Abstractions; 3 | using ShardingCore.Sharding; 4 | using ShardingCore.Sharding.Abstractions; 5 | using ShardingCoreMultiTenantSys.TenantSys.Domain.Maps; 6 | 7 | namespace ShardingCoreMultiTenantSys.DbContexts 8 | { 9 | public class TenantDbContext:AbstractShardingDbContext,IShardingTableDbContext 10 | { 11 | public TenantDbContext(DbContextOptions options) : base(options) 12 | { 13 | } 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | base.OnModelCreating(modelBuilder); 18 | modelBuilder.ApplyConfiguration(new OrderMap()); 19 | } 20 | 21 | public IRouteTail RouteTail { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/Domain/Maps/SysUserMap.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities; 4 | 5 | namespace ShardingCoreMultiTenantSys.IdentitySys.Domain.Maps 6 | { 7 | public class SysUserMap:IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(o => o.Id); 12 | builder.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50); 13 | builder.Property(o => o.Name).IsRequired().HasMaxLength(50); 14 | builder.Property(o => o.Password).IsRequired().IsUnicode(false).HasMaxLength(50); 15 | builder.HasQueryFilter(o => o.IsDeleted == false); 16 | builder.ToTable(nameof(SysUser)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/Domain/Maps/SysUserTenantConfigMap.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities; 4 | 5 | namespace ShardingCoreMultiTenantSys.IdentitySys.Domain.Maps 6 | { 7 | public class SysUserTenantConfigMap:IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(o => o.Id); 12 | builder.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50); 13 | builder.Property(o => o.UserId).IsRequired().IsUnicode(false).HasMaxLength(50); 14 | builder.Property(o => o.ConfigJson).IsRequired().HasMaxLength(2000); 15 | builder.HasQueryFilter(o => o.IsDeleted == false); 16 | builder.ToTable(nameof(SysUserTenantConfig)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:1089", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "ShardingCoreMultiTenantSys": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "weatherforecast", 17 | "applicationUrl": "http://localhost:5000", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "weatherforecast", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Extensions/MigrationExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using ShardingCoreMultiTenantSys.MigrationsAssemblies; 4 | 5 | namespace ShardingCoreMultiTenantSys.Extensions; 6 | 7 | public static class MigrationExtension 8 | { 9 | 10 | public static DbContextOptionsBuilder UseMigrationNamespace(this DbContextOptionsBuilder optionsBuilder,IMigrationNamespace migrationNamespace) 11 | { 12 | var shardingWrapExtension = optionsBuilder.CreateOrGetExtension(migrationNamespace); 13 | ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(shardingWrapExtension); 14 | return optionsBuilder; 15 | } 16 | 17 | private static MigrationNamespaceExtension CreateOrGetExtension( 18 | this DbContextOptionsBuilder optionsBuilder, IMigrationNamespace migrationNamespace) 19 | => optionsBuilder.Options.FindExtension() ?? 20 | new MigrationNamespaceExtension(migrationNamespace); 21 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Tenants/ITenantManager.cs: -------------------------------------------------------------------------------- 1 | using ShardingCore.Core.RuntimeContexts; 2 | 3 | namespace ShardingCoreMultiTenantSys.Tenants 4 | { 5 | public interface ITenantManager 6 | { 7 | /// 8 | /// 获取所有的租户 9 | /// 10 | /// 11 | List GetAll(); 12 | /// 13 | /// 获取当前租户 14 | /// 15 | /// 16 | TenantContext GetCurrentTenantContext(); 17 | /// 18 | /// 添加租户信息 19 | /// 20 | /// 21 | /// 22 | /// 23 | bool AddTenantSharding(string tenantId, IShardingRuntimeContext shardingRuntimeContext); 24 | 25 | /// 26 | /// 创建租户环境 27 | /// 28 | /// 29 | /// 30 | TenantScope CreateScope(string tenantId); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/ShardingConfigs/ShardingTenantOptions.cs: -------------------------------------------------------------------------------- 1 | using ShardingCore.Sharding.ReadWriteConfigurations; 2 | 3 | namespace ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs 4 | { 5 | public class ShardingTenantOptions 6 | { 7 | /// 8 | /// 默认数据源名称 9 | /// 10 | public string DefaultDataSourceName { get; set;} 11 | /// 12 | /// 默认数据库地址 13 | /// 14 | public string DefaultConnectionString { get; set; } 15 | /// 16 | /// 数据库类型 17 | /// 18 | public DbTypeEnum DbType { get; set; } 19 | /// 20 | /// 分片模式 取模还是按月 21 | /// 22 | public OrderShardingTypeEnum OrderShardingType { get; set; } 23 | /// 24 | /// 按月分片其实时间 25 | /// 26 | public DateTime BeginTimeForSharding { get; set; } 27 | /// 28 | /// 分片迁移的命名空间 29 | /// 30 | public string MigrationNamespace { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace ShardingCoreMultiTenantSys.Controllers 4 | { 5 | [ApiController] 6 | [Route("[controller]")] 7 | public class WeatherForecastController : ControllerBase 8 | { 9 | private static readonly string[] Summaries = new[] 10 | { 11 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 12 | }; 13 | 14 | private readonly ILogger _logger; 15 | 16 | public WeatherForecastController(ILogger logger) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | [HttpGet] 22 | public IEnumerable Get() 23 | { 24 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 25 | { 26 | Date = DateTime.Now.AddDays(index), 27 | TemperatureC = Random.Shared.Next(-20, 55), 28 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 29 | }) 30 | .ToArray(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShardingCoreMultiTenantSys", "ShardingCoreMultiTenantSys\ShardingCoreMultiTenantSys.csproj", "{13ADBD03-1AA9-4106-9BDD-8E687D7B719F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {13ADBD03-1AA9-4106-9BDD-8E687D7B719F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {13ADBD03-1AA9-4106-9BDD-8E687D7B719F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {13ADBD03-1AA9-4106-9BDD-8E687D7B719F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {13ADBD03-1AA9-4106-9BDD-8E687D7B719F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {7BF2242D-18C4-42E7-8D2C-D54E6D51B0B7} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/TenantSys/Shardings/OrderMonthTableRoute.cs: -------------------------------------------------------------------------------- 1 | using ShardingCore.Core.EntityMetadatas; 2 | using ShardingCore.VirtualRoutes.Abstractions; 3 | using ShardingCore.VirtualRoutes.Mods; 4 | using ShardingCore.VirtualRoutes.Months; 5 | using ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs; 6 | using ShardingCoreMultiTenantSys.TenantSys.Domain.Entities; 7 | 8 | namespace ShardingCoreMultiTenantSys.TenantSys.Shardings 9 | { 10 | public class OrderMonthTableRoute:AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute 11 | { 12 | private readonly ShardingTenantOptions _shardingTenantOptions; 13 | 14 | public OrderMonthTableRoute(ShardingTenantOptions shardingTenantOptions) 15 | { 16 | _shardingTenantOptions = shardingTenantOptions; 17 | } 18 | public override void Configure(EntityMetadataTableBuilder builder) 19 | { 20 | builder.ShardingProperty(o => o.CreationTime); 21 | } 22 | 23 | public override bool AutoCreateTableByTime() 24 | { 25 | return true; 26 | 27 | } 28 | 29 | public override DateTime GetBeginTime() 30 | { 31 | return _shardingTenantOptions.BeginTimeForSharding; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/ShardingCoreMultiTenantSys.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | all 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/SqlServer/20220724043120_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ShardingCoreMultiTenantSys.Migrations.SqlServer 7 | { 8 | public partial class InitialCreate : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "Order", 14 | columns: table => new 15 | { 16 | Id = table.Column(type: "varchar(50)", unicode: false, maxLength: 50, nullable: false), 17 | Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), 18 | CreationTime = table.Column(type: "datetime2", nullable: false), 19 | IsDeleted = table.Column(type: "bit", nullable: false) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_Order", x => x.Id); 24 | }); 25 | } 26 | 27 | protected override void Down(MigrationBuilder migrationBuilder) 28 | { 29 | migrationBuilder.DropTable( 30 | name: "Order"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/TenantSys/Shardings/ShardingSqlServerMigrationsSqlGenerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | using Microsoft.EntityFrameworkCore.Migrations.Operations; 4 | using ShardingCore.Core.RuntimeContexts; 5 | using ShardingCore.Helpers; 6 | 7 | namespace ShardingCoreMultiTenantSys.TenantSys.Shardings; 8 | 9 | public class ShardingSqlServerMigrationsSqlGenerator:SqlServerMigrationsSqlGenerator 10 | { 11 | private readonly IShardingRuntimeContext _shardingRuntimeContext; 12 | 13 | public ShardingSqlServerMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IRelationalAnnotationProvider migrationsAnnotations,IShardingRuntimeContext shardingRuntimeContext) : base(dependencies, migrationsAnnotations) 14 | { 15 | _shardingRuntimeContext = shardingRuntimeContext; 16 | } 17 | protected override void Generate( 18 | MigrationOperation operation, 19 | IModel model, 20 | MigrationCommandListBuilder builder) 21 | { 22 | var oldCmds = builder.GetCommandList().ToList(); 23 | base.Generate(operation, model, builder); 24 | var newCmds = builder.GetCommandList().ToList(); 25 | var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList(); 26 | 27 | MigrationHelper.Generate(_shardingRuntimeContext,operation, builder, Dependencies.SqlGenerationHelper, addCmds); 28 | } 29 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Tenants/DefaultTenantManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using ShardingCore.Core.RuntimeContexts; 3 | 4 | namespace ShardingCoreMultiTenantSys.Tenants 5 | { 6 | public class DefaultTenantManager:ITenantManager 7 | { 8 | private readonly ITenantContextAccessor _tenantContextAccessor; 9 | private readonly ConcurrentDictionary _cache = new(); 10 | 11 | public DefaultTenantManager(ITenantContextAccessor tenantContextAccessor) 12 | { 13 | _tenantContextAccessor = tenantContextAccessor; 14 | } 15 | 16 | public List GetAll() 17 | { 18 | return _cache.Keys.ToList(); 19 | } 20 | 21 | public TenantContext GetCurrentTenantContext() 22 | { 23 | return _tenantContextAccessor.TenantContext; 24 | } 25 | 26 | public bool AddTenantSharding(string tenantId, IShardingRuntimeContext shardingRuntimeContext) 27 | { 28 | return _cache.TryAdd(tenantId, shardingRuntimeContext); 29 | } 30 | 31 | public TenantScope CreateScope(string tenantId) 32 | { 33 | if (!_cache.TryGetValue(tenantId, out var shardingRuntimeContext)) 34 | { 35 | throw new InvalidOperationException("未找到对应租户的配置"); 36 | } 37 | 38 | _tenantContextAccessor.TenantContext = new TenantContext(shardingRuntimeContext); 39 | return new TenantScope(_tenantContextAccessor); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/MyMigrationsScaffolder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Internal; 2 | using Microsoft.EntityFrameworkCore.Migrations.Design; 3 | 4 | namespace ShardingCoreMultiTenantSys.MigrationsAssemblies; 5 | 6 | public class MyMigrationsScaffolder: MigrationsScaffolder 7 | { 8 | private readonly Type _contextType; 9 | public MyMigrationsScaffolder(MigrationsScaffolderDependencies dependencies) : base(dependencies) 10 | { 11 | _contextType = dependencies.CurrentContext.Context.GetType(); 12 | } 13 | protected override string GetDirectory(string projectDir, string? siblingFileName, string subnamespace) 14 | { 15 | var defaultDirectory = Path.Combine(projectDir, Path.Combine(subnamespace.Split('.'))); 16 | 17 | if (siblingFileName != null) 18 | { 19 | if (!siblingFileName.StartsWith(_contextType.Name + "ModelSnapshot.")) 20 | { 21 | var siblingPath = TryGetProjectFile(projectDir, siblingFileName); 22 | if (siblingPath != null) 23 | { 24 | var lastDirectory = Path.GetDirectoryName(siblingPath)!; 25 | if (!defaultDirectory.Equals(lastDirectory, StringComparison.OrdinalIgnoreCase)) 26 | { 27 | Dependencies.OperationReporter.WriteVerbose(DesignStrings.ReusingNamespace(siblingFileName)); 28 | 29 | return lastDirectory; 30 | } 31 | } 32 | } 33 | } 34 | 35 | return defaultDirectory; 36 | } 37 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/MySql/20220724043347_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ShardingCoreMultiTenantSys.Migrations.MySql 7 | { 8 | public partial class InitialCreate : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.AlterDatabase() 13 | .Annotation("MySql:CharSet", "utf8mb4"); 14 | 15 | migrationBuilder.CreateTable( 16 | name: "Order", 17 | columns: table => new 18 | { 19 | Id = table.Column(type: "varchar(50)", unicode: false, maxLength: 50, nullable: false) 20 | .Annotation("MySql:CharSet", "utf8mb4"), 21 | Name = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) 22 | .Annotation("MySql:CharSet", "utf8mb4"), 23 | CreationTime = table.Column(type: "datetime", nullable: false), 24 | IsDeleted = table.Column(type: "tinyint(1)", nullable: false) 25 | }, 26 | constraints: table => 27 | { 28 | table.PrimaryKey("PK_Order", x => x.Id); 29 | }) 30 | .Annotation("MySql:CharSet", "utf8mb4"); 31 | } 32 | 33 | protected override void Down(MigrationBuilder migrationBuilder) 34 | { 35 | migrationBuilder.DropTable( 36 | name: "Order"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/TenantSys/Shardings/ShardingMySqlMigrationsSqlGenerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | using Microsoft.EntityFrameworkCore.Migrations.Operations; 4 | using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal; 5 | using Pomelo.EntityFrameworkCore.MySql.Migrations; 6 | using ShardingCore.Core.RuntimeContexts; 7 | using ShardingCore.Helpers; 8 | 9 | namespace ShardingCoreMultiTenantSys.TenantSys.Shardings 10 | { 11 | public class ShardingMySqlMigrationsSqlGenerator:MySqlMigrationsSqlGenerator 12 | { 13 | private readonly IShardingRuntimeContext _shardingRuntimeContext; 14 | 15 | public ShardingMySqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IRelationalAnnotationProvider annotationProvider, IMySqlOptions options,IShardingRuntimeContext shardingRuntimeContext) : base(dependencies, annotationProvider, options) 16 | { 17 | _shardingRuntimeContext = shardingRuntimeContext; 18 | } 19 | protected override void Generate( 20 | MigrationOperation operation, 21 | IModel model, 22 | MigrationCommandListBuilder builder) 23 | { 24 | var oldCmds = builder.GetCommandList().ToList(); 25 | base.Generate(operation, model, builder); 26 | var newCmds = builder.GetCommandList().ToList(); 27 | var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList(); 28 | 29 | MigrationHelper.Generate(_shardingRuntimeContext,operation, builder, Dependencies.SqlGenerationHelper, addCmds); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/MySql/TenantDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using ShardingCoreMultiTenantSys.DbContexts; 7 | 8 | #nullable disable 9 | 10 | namespace ShardingCoreMultiTenantSys.Migrations.MySql 11 | { 12 | [DbContext(typeof(TenantDbContext))] 13 | partial class TenantDbContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "6.0.7") 20 | .HasAnnotation("Relational:MaxIdentifierLength", 64); 21 | 22 | modelBuilder.Entity("ShardingCoreMultiTenantSys.TenantSys.Domain.Entities.Order", b => 23 | { 24 | b.Property("Id") 25 | .HasMaxLength(50) 26 | .IsUnicode(false) 27 | .HasColumnType("varchar(50)"); 28 | 29 | b.Property("CreationTime") 30 | .HasColumnType("datetime"); 31 | 32 | b.Property("IsDeleted") 33 | .HasColumnType("tinyint(1)"); 34 | 35 | b.Property("Name") 36 | .IsRequired() 37 | .HasMaxLength(100) 38 | .HasColumnType("varchar(100)"); 39 | 40 | b.HasKey("Id"); 41 | 42 | b.ToTable("Order", (string)null); 43 | }); 44 | #pragma warning restore 612, 618 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/MigrationNamespaceExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | 3 | namespace ShardingCoreMultiTenantSys.MigrationsAssemblies; 4 | 5 | public class MigrationNamespaceExtension : IDbContextOptionsExtension 6 | { 7 | public IMigrationNamespace MigrationNamespace { get; } 8 | 9 | public MigrationNamespaceExtension(IMigrationNamespace migrationNamespace) 10 | { 11 | MigrationNamespace = migrationNamespace; 12 | } 13 | 14 | public void ApplyServices(IServiceCollection services) 15 | { 16 | services.AddSingleton(sp => MigrationNamespace); 17 | } 18 | 19 | public void Validate(IDbContextOptions options) 20 | { 21 | } 22 | 23 | 24 | public DbContextOptionsExtensionInfo Info => new MigrationNamespaceExtensionInfo(this); 25 | 26 | private class MigrationNamespaceExtensionInfo : DbContextOptionsExtensionInfo 27 | { 28 | private readonly MigrationNamespaceExtension _migrationNamespaceExtension; 29 | 30 | public MigrationNamespaceExtensionInfo(IDbContextOptionsExtension extension) : base(extension) 31 | { 32 | _migrationNamespaceExtension = (MigrationNamespaceExtension)extension; 33 | } 34 | 35 | public override int GetServiceProviderHashCode() => 36 | _migrationNamespaceExtension.MigrationNamespace.GetNamespace().GetHashCode(); 37 | 38 | public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => true; 39 | 40 | public override void PopulateDebugInfo(IDictionary debugInfo) 41 | { 42 | } 43 | 44 | public override bool IsDatabaseProvider => false; 45 | public override string LogFragment => "MigrationNamespaceExtension"; 46 | } 47 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Controllers/TenantSys/TenantController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.EntityFrameworkCore; 4 | using ShardingCoreMultiTenantSys.DbContexts; 5 | using ShardingCoreMultiTenantSys.TenantSys.Domain.Entities; 6 | 7 | namespace ShardingCoreMultiTenantSys.Controllers.TenantSys 8 | { 9 | [Route("api/tenant/[controller]/[action]")] 10 | [ApiController] 11 | [Authorize(AuthenticationSchemes = "Bearer")] 12 | public class TenantController : ControllerBase 13 | { 14 | private readonly TenantDbContext _tenantDbContext; 15 | 16 | public TenantController(TenantDbContext tenantDbContext) 17 | { 18 | _tenantDbContext = tenantDbContext; 19 | } 20 | public async Task AddOrder() 21 | { 22 | var order = new Order() 23 | { 24 | Id = Guid.NewGuid().ToString("n"), 25 | CreationTime = DateTime.Now, 26 | Name = new Random().Next(1,100)+"_name" 27 | }; 28 | await _tenantDbContext.AddAsync(order); 29 | await _tenantDbContext.SaveChangesAsync(); 30 | return Ok(order.Id); 31 | } 32 | public async Task UpdateOrder([FromQuery]string id) 33 | { 34 | var order =await _tenantDbContext.Set().FirstOrDefaultAsync(o=>o.Id==id); 35 | if (order == null) return BadRequest(); 36 | order.Name = new Random().Next(1, 100) + "_name"; 37 | await _tenantDbContext.SaveChangesAsync(); 38 | return Ok(order.Id); 39 | } 40 | public async Task GetOrders() 41 | { 42 | var orders =await _tenantDbContext.Set().ToListAsync(); 43 | return Ok(orders); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/MySql/20220724043347_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ShardingCoreMultiTenantSys.DbContexts; 8 | 9 | #nullable disable 10 | 11 | namespace ShardingCoreMultiTenantSys.Migrations.MySql 12 | { 13 | [DbContext(typeof(TenantDbContext))] 14 | [Migration("20220724043347_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "6.0.7") 22 | .HasAnnotation("Relational:MaxIdentifierLength", 64); 23 | 24 | modelBuilder.Entity("ShardingCoreMultiTenantSys.TenantSys.Domain.Entities.Order", b => 25 | { 26 | b.Property("Id") 27 | .HasMaxLength(50) 28 | .IsUnicode(false) 29 | .HasColumnType("varchar(50)"); 30 | 31 | b.Property("CreationTime") 32 | .HasColumnType("datetime"); 33 | 34 | b.Property("IsDeleted") 35 | .HasColumnType("tinyint(1)"); 36 | 37 | b.Property("Name") 38 | .IsRequired() 39 | .HasMaxLength(100) 40 | .HasColumnType("varchar(100)"); 41 | 42 | b.HasKey("Id"); 43 | 44 | b.ToTable("Order", (string)null); 45 | }); 46 | #pragma warning restore 612, 618 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/SqlServer/TenantDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ShardingCoreMultiTenantSys.DbContexts; 8 | 9 | #nullable disable 10 | 11 | namespace ShardingCoreMultiTenantSys.Migrations.SqlServer 12 | { 13 | [DbContext(typeof(TenantDbContext))] 14 | partial class TenantDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "6.0.7") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 24 | 25 | modelBuilder.Entity("ShardingCoreMultiTenantSys.TenantSys.Domain.Entities.Order", b => 26 | { 27 | b.Property("Id") 28 | .HasMaxLength(50) 29 | .IsUnicode(false) 30 | .HasColumnType("varchar(50)"); 31 | 32 | b.Property("CreationTime") 33 | .HasColumnType("datetime2"); 34 | 35 | b.Property("IsDeleted") 36 | .HasColumnType("bit"); 37 | 38 | b.Property("Name") 39 | .IsRequired() 40 | .HasMaxLength(100) 41 | .HasColumnType("nvarchar(100)"); 42 | 43 | b.HasKey("Id"); 44 | 45 | b.ToTable("Order", (string)null); 46 | }); 47 | #pragma warning restore 612, 618 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/SqlServer/20220724043120_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using ShardingCoreMultiTenantSys.DbContexts; 9 | 10 | #nullable disable 11 | 12 | namespace ShardingCoreMultiTenantSys.Migrations.SqlServer 13 | { 14 | [DbContext(typeof(TenantDbContext))] 15 | [Migration("20220724043120_InitialCreate")] 16 | partial class InitialCreate 17 | { 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "6.0.7") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 26 | 27 | modelBuilder.Entity("ShardingCoreMultiTenantSys.TenantSys.Domain.Entities.Order", b => 28 | { 29 | b.Property("Id") 30 | .HasMaxLength(50) 31 | .IsUnicode(false) 32 | .HasColumnType("varchar(50)"); 33 | 34 | b.Property("CreationTime") 35 | .HasColumnType("datetime2"); 36 | 37 | b.Property("IsDeleted") 38 | .HasColumnType("bit"); 39 | 40 | b.Property("Name") 41 | .IsRequired() 42 | .HasMaxLength(100) 43 | .HasColumnType("nvarchar(100)"); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.ToTable("Order", (string)null); 48 | }); 49 | #pragma warning restore 612, 618 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Middlewares/TenantSelectMiddleware.cs: -------------------------------------------------------------------------------- 1 | using ShardingCore.Core.VirtualDatabase.VirtualDataSources.Abstractions; 2 | using ShardingCoreMultiTenantSys.DbContexts; 3 | using ShardingCoreMultiTenantSys.Tenants; 4 | 5 | namespace ShardingCoreMultiTenantSys.Middlewares 6 | { 7 | public class TenantSelectMiddleware 8 | { 9 | private readonly RequestDelegate _next; 10 | private readonly ITenantManager _tenantManager; 11 | 12 | public TenantSelectMiddleware(RequestDelegate next,ITenantManager tenantManager) 13 | { 14 | _next = next; 15 | _tenantManager = tenantManager; 16 | } 17 | 18 | /// 19 | /// 1.中间件的方法必须叫Invoke,且为public,非static。 20 | /// 2.Invoke方法第一个参数必须是HttpContext类型。 21 | /// 3.Invoke方法必须返回Task。 22 | /// 4.Invoke方法可以有多个参数,除HttpContext外其它参数会尝试从依赖注入容器中获取。 23 | /// 5.Invoke方法不能有重载。 24 | /// 25 | /// Author : Napoleon 26 | /// Created : 2020/1/30 21:30 27 | public async Task Invoke(HttpContext context) 28 | { 29 | 30 | if (context.Request.Path.ToString().StartsWith("/api/tenant", StringComparison.CurrentCultureIgnoreCase)) 31 | { 32 | if (!context.User.Identity.IsAuthenticated) 33 | { 34 | await _next(context); 35 | return; 36 | } 37 | 38 | var tenantId = context.User.Claims.FirstOrDefault((o) => o.Type == "uid")?.Value; 39 | if (string.IsNullOrWhiteSpace(tenantId)) 40 | { 41 | await DoUnAuthorized(context, "not found tenant id"); 42 | return; 43 | } 44 | 45 | using (_tenantManager.CreateScope(tenantId)) 46 | { 47 | await _next(context); 48 | } 49 | } 50 | else 51 | { 52 | await _next(context); 53 | } 54 | } 55 | 56 | private async Task DoUnAuthorized(HttpContext context, string msg) 57 | { 58 | context.Response.StatusCode = 403; 59 | await context.Response.WriteAsync(msg); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/Identity/20220724042931_IdentityInit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ShardingCoreMultiTenantSys.Migrations.Identity 7 | { 8 | public partial class IdentityInit : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "SysUser", 14 | columns: table => new 15 | { 16 | Id = table.Column(type: "varchar(50)", unicode: false, maxLength: 50, nullable: false), 17 | Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), 18 | Password = table.Column(type: "varchar(50)", unicode: false, maxLength: 50, nullable: false), 19 | CreationTime = table.Column(type: "datetime2", nullable: false), 20 | IsDeleted = table.Column(type: "bit", nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_SysUser", x => x.Id); 25 | }); 26 | 27 | migrationBuilder.CreateTable( 28 | name: "SysUserTenantConfig", 29 | columns: table => new 30 | { 31 | Id = table.Column(type: "varchar(50)", unicode: false, maxLength: 50, nullable: false), 32 | UserId = table.Column(type: "varchar(50)", unicode: false, maxLength: 50, nullable: false), 33 | ConfigJson = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), 34 | CreationTime = table.Column(type: "datetime2", nullable: false), 35 | IsDeleted = table.Column(type: "bit", nullable: false) 36 | }, 37 | constraints: table => 38 | { 39 | table.PrimaryKey("PK_SysUserTenantConfig", x => x.Id); 40 | }); 41 | } 42 | 43 | protected override void Down(MigrationBuilder migrationBuilder) 44 | { 45 | migrationBuilder.DropTable( 46 | name: "SysUser"); 47 | 48 | migrationBuilder.DropTable( 49 | name: "SysUserTenantConfig"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Extensions/TenantExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Newtonsoft.Json; 3 | using ShardingCore.Extensions; 4 | using ShardingCore.Helpers; 5 | using ShardingCoreMultiTenantSys.DbContexts; 6 | using ShardingCoreMultiTenantSys.IdentitySys; 7 | using ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities; 8 | using ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs; 9 | using ShardingCoreMultiTenantSys.Tenants; 10 | 11 | namespace ShardingCoreMultiTenantSys.Extensions 12 | { 13 | public static class TenantExtension 14 | { 15 | public static void InitTenant(this IServiceProvider serviceProvider) 16 | { 17 | var tenantManager = serviceProvider.GetRequiredService(); 18 | var shardingBuilder = serviceProvider.GetRequiredService(); 19 | 20 | using (var scope = serviceProvider.CreateScope()) 21 | { 22 | var identityDbContext = scope.ServiceProvider.GetRequiredService(); 23 | identityDbContext.Database.Migrate(); 24 | var sysUserTenantConfigs = identityDbContext.Set().ToList(); 25 | if (sysUserTenantConfigs.Any()) 26 | { 27 | foreach (var sysUserTenantConfig in sysUserTenantConfigs) 28 | { 29 | var shardingTenantOptions = JsonConvert.DeserializeObject(sysUserTenantConfig.ConfigJson); 30 | 31 | var shardingRuntimeContext = shardingBuilder.Build(shardingTenantOptions); 32 | 33 | tenantManager.AddTenantSharding(sysUserTenantConfig.UserId, shardingRuntimeContext); 34 | } 35 | } 36 | } 37 | 38 | var tenantIds = tenantManager.GetAll(); 39 | foreach (var tenantId in tenantIds) 40 | { 41 | using(tenantManager.CreateScope(tenantId)) 42 | using (var scope = serviceProvider.CreateScope()) 43 | { 44 | var shardingRuntimeContext = tenantManager.GetCurrentTenantContext().GetShardingRuntimeContext(); 45 | //开启定时任务 46 | shardingRuntimeContext.UseAutoShardingCreate(); 47 | var tenantDbContext = scope.ServiceProvider.GetService(); 48 | tenantDbContext.Database.Migrate(); 49 | //补偿表 50 | shardingRuntimeContext.UseAutoTryCompensateTable(); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/Identity/IdentityDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ShardingCoreMultiTenantSys.IdentitySys; 8 | 9 | #nullable disable 10 | 11 | namespace ShardingCoreMultiTenantSys.Migrations.Identity 12 | { 13 | [DbContext(typeof(IdentityDbContext))] 14 | partial class IdentityDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "6.0.7") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 24 | 25 | modelBuilder.Entity("ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities.SysUser", b => 26 | { 27 | b.Property("Id") 28 | .HasMaxLength(50) 29 | .IsUnicode(false) 30 | .HasColumnType("varchar(50)"); 31 | 32 | b.Property("CreationTime") 33 | .HasColumnType("datetime2"); 34 | 35 | b.Property("IsDeleted") 36 | .HasColumnType("bit"); 37 | 38 | b.Property("Name") 39 | .IsRequired() 40 | .HasMaxLength(50) 41 | .HasColumnType("nvarchar(50)"); 42 | 43 | b.Property("Password") 44 | .IsRequired() 45 | .HasMaxLength(50) 46 | .IsUnicode(false) 47 | .HasColumnType("varchar(50)"); 48 | 49 | b.HasKey("Id"); 50 | 51 | b.ToTable("SysUser", (string)null); 52 | }); 53 | 54 | modelBuilder.Entity("ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities.SysUserTenantConfig", b => 55 | { 56 | b.Property("Id") 57 | .HasMaxLength(50) 58 | .IsUnicode(false) 59 | .HasColumnType("varchar(50)"); 60 | 61 | b.Property("ConfigJson") 62 | .IsRequired() 63 | .HasMaxLength(2000) 64 | .HasColumnType("nvarchar(2000)"); 65 | 66 | b.Property("CreationTime") 67 | .HasColumnType("datetime2"); 68 | 69 | b.Property("IsDeleted") 70 | .HasColumnType("bit"); 71 | 72 | b.Property("UserId") 73 | .IsRequired() 74 | .HasMaxLength(50) 75 | .IsUnicode(false) 76 | .HasColumnType("varchar(50)"); 77 | 78 | b.HasKey("Id"); 79 | 80 | b.ToTable("SysUserTenantConfig", (string)null); 81 | }); 82 | #pragma warning restore 612, 618 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Migrations/Identity/20220724042931_IdentityInit.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using ShardingCoreMultiTenantSys.IdentitySys; 9 | 10 | #nullable disable 11 | 12 | namespace ShardingCoreMultiTenantSys.Migrations.Identity 13 | { 14 | [DbContext(typeof(IdentityDbContext))] 15 | [Migration("20220724042931_IdentityInit")] 16 | partial class IdentityInit 17 | { 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "6.0.7") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 26 | 27 | modelBuilder.Entity("ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities.SysUser", b => 28 | { 29 | b.Property("Id") 30 | .HasMaxLength(50) 31 | .IsUnicode(false) 32 | .HasColumnType("varchar(50)"); 33 | 34 | b.Property("CreationTime") 35 | .HasColumnType("datetime2"); 36 | 37 | b.Property("IsDeleted") 38 | .HasColumnType("bit"); 39 | 40 | b.Property("Name") 41 | .IsRequired() 42 | .HasMaxLength(50) 43 | .HasColumnType("nvarchar(50)"); 44 | 45 | b.Property("Password") 46 | .IsRequired() 47 | .HasMaxLength(50) 48 | .IsUnicode(false) 49 | .HasColumnType("varchar(50)"); 50 | 51 | b.HasKey("Id"); 52 | 53 | b.ToTable("SysUser", (string)null); 54 | }); 55 | 56 | modelBuilder.Entity("ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities.SysUserTenantConfig", b => 57 | { 58 | b.Property("Id") 59 | .HasMaxLength(50) 60 | .IsUnicode(false) 61 | .HasColumnType("varchar(50)"); 62 | 63 | b.Property("ConfigJson") 64 | .IsRequired() 65 | .HasMaxLength(2000) 66 | .HasColumnType("nvarchar(2000)"); 67 | 68 | b.Property("CreationTime") 69 | .HasColumnType("datetime2"); 70 | 71 | b.Property("IsDeleted") 72 | .HasColumnType("bit"); 73 | 74 | b.Property("UserId") 75 | .IsRequired() 76 | .HasMaxLength(50) 77 | .IsUnicode(false) 78 | .HasColumnType("varchar(50)"); 79 | 80 | b.HasKey("Id"); 81 | 82 | b.ToTable("SysUserTenantConfig", (string)null); 83 | }); 84 | #pragma warning restore 612, 618 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | using Microsoft.IdentityModel.Tokens; 5 | using ShardingCore; 6 | using ShardingCore.Bootstrappers; 7 | using ShardingCore.Extensions; 8 | using ShardingCoreMultiTenantSys.DbContexts; 9 | using ShardingCoreMultiTenantSys.Extensions; 10 | using ShardingCoreMultiTenantSys.IdentitySys; 11 | using ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs; 12 | using ShardingCoreMultiTenantSys.Middlewares; 13 | using ShardingCoreMultiTenantSys.MigrationsAssemblies; 14 | using ShardingCoreMultiTenantSys.Tenants; 15 | using ShardingCoreMultiTenantSys.TenantSys.Shardings; 16 | 17 | var builder = WebApplication.CreateBuilder(args); 18 | 19 | // Add services to the container. 20 | 21 | builder.Services.AddControllers(); 22 | builder.Services.AddAuthentication(); 23 | 24 | #region 用户系统配置 25 | // Add-Migration IdentityInit -Context IdentityDbContext -OutputDir Migrations\Identity 26 | builder.Services.AddDbContext(o => 27 | o.UseSqlServer("Data Source=localhost;Initial Catalog=IdDb;Integrated Security=True;")); 28 | //生成密钥 29 | var keyByteArray = Encoding.ASCII.GetBytes("123123!@#!@#123123"); 30 | var signingKey = new SymmetricSecurityKey(keyByteArray); 31 | //认证参数 32 | builder.Services.AddAuthentication("Bearer") 33 | .AddJwtBearer(o => 34 | { 35 | o.TokenValidationParameters = new TokenValidationParameters 36 | { 37 | ValidateIssuerSigningKey = true, 38 | IssuerSigningKey = signingKey, 39 | ValidateIssuer = true, 40 | ValidIssuer = "https://localhost:5000", 41 | ValidateAudience = true, 42 | ValidAudience = "api", 43 | ValidateLifetime = true, 44 | ClockSkew = TimeSpan.Zero, 45 | RequireExpirationTime = true, 46 | }; 47 | }); 48 | 49 | #endregion 50 | 51 | builder.Services.AddSingleton(); 52 | builder.Services.AddSingleton(); 53 | builder.Services.AddSingleton(); 54 | 55 | #region 配置ShardingCore 56 | 57 | var provider = builder.Configuration.GetValue("Provider", "UnKnown"); 58 | //Add-Migration InitialCreate -Context TenantDbContext -OutputDir Migrations\SqlServer -Args "--provider SqlServer" 59 | //Add-Migration InitialCreate -Context TenantDbContext -OutputDir Migrations\MySql -Args "--provider MySql" 60 | builder.Services.AddDbContext((sp, b) => 61 | { 62 | var tenantManager = sp.GetRequiredService(); 63 | var currentTenantContext = tenantManager.GetCurrentTenantContext(); 64 | //如果有上下文那么创建租户dbcontext否则就是启动命令Add-Migration 65 | if (currentTenantContext != null) 66 | { 67 | var shardingRuntimeContext = currentTenantContext.GetShardingRuntimeContext(); 68 | b.UseDefaultSharding(shardingRuntimeContext); 69 | } 70 | if (args.IsNotEmpty()) 71 | { 72 | //命令启动时为了保证Add-Migration正常运行 73 | if (provider == "MySql") 74 | { 75 | b.UseMySql("server=127.0.0.1;port=3306;database=TenantDb;userid=root;password=L6yBtV6qNENrwBy7;", 76 | new MySqlServerVersion(new Version())) 77 | .UseMigrationNamespace(new MySqlMigrationNamespace()) 78 | .ReplaceService(); 79 | return; 80 | } 81 | 82 | if (provider == "SqlServer") 83 | { 84 | b.UseSqlServer("Data Source=localhost;Initial Catalog=TenantDb;Integrated Security=True;") 85 | .UseMigrationNamespace(new SqlServerMigrationNamespace()) 86 | .ReplaceService(); 87 | return; 88 | } 89 | } 90 | }); 91 | 92 | #endregion 93 | 94 | 95 | var app = builder.Build(); 96 | 97 | //初始化启动配置租户信息 98 | app.Services.InitTenant(); 99 | app.UseAuthorization(); 100 | 101 | //在认证后启用租户选择中间件 102 | app.UseMiddleware(); 103 | 104 | app.MapControllers(); 105 | 106 | app.Run(); -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/IdentitySys/ShardingConfigs/DefaultShardingBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | using ShardingCore; 4 | using ShardingCore.Core.RuntimeContexts; 5 | using ShardingCore.TableExists; 6 | using ShardingCore.TableExists.Abstractions; 7 | using ShardingCoreMultiTenantSys.DbContexts; 8 | using ShardingCoreMultiTenantSys.Extensions; 9 | using ShardingCoreMultiTenantSys.MigrationsAssemblies; 10 | using ShardingCoreMultiTenantSys.TenantSys.Shardings; 11 | 12 | namespace ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs; 13 | 14 | public class DefaultShardingBuilder:IShardingBuilder 15 | { 16 | public static readonly ILoggerFactory efLogger = LoggerFactory.Create(builder => 17 | { 18 | builder.AddFilter((category, level) => 19 | category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole(); 20 | }); 21 | private readonly IServiceProvider _serviceProvider; 22 | 23 | public DefaultShardingBuilder(IServiceProvider serviceProvider) 24 | { 25 | _serviceProvider = serviceProvider; 26 | } 27 | public IShardingRuntimeContext Build(ShardingTenantOptions tenantOptions) 28 | { 29 | var shardingRuntimeBuilder = new ShardingRuntimeBuilder() 30 | .UseRouteConfig(o => 31 | { 32 | if (tenantOptions.OrderShardingType == OrderShardingTypeEnum.Mod) 33 | { 34 | o.AddShardingTableRoute(); 35 | } 36 | if (tenantOptions.OrderShardingType == OrderShardingTypeEnum.ByMonth) 37 | { 38 | o.AddShardingTableRoute(); 39 | } 40 | }).UseConfig(o => 41 | { 42 | o.ThrowIfQueryRouteNotMatch = false; 43 | o.UseShardingQuery((conStr, builder) => 44 | { 45 | if (tenantOptions.DbType == DbTypeEnum.MYSQL) 46 | { 47 | builder.UseMySql(conStr, new MySqlServerVersion(new Version())) 48 | .UseMigrationNamespace(new MySqlMigrationNamespace()); 49 | } 50 | if (tenantOptions.DbType == DbTypeEnum.MSSQL) 51 | { 52 | builder.UseSqlServer(conStr) 53 | .UseMigrationNamespace(new SqlServerMigrationNamespace()); 54 | } 55 | builder.UseLoggerFactory(efLogger) 56 | .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) 57 | .ReplaceService(); 58 | }); 59 | o.UseShardingTransaction((connection, builder) => 60 | { 61 | if (tenantOptions.DbType == DbTypeEnum.MYSQL) 62 | { 63 | builder 64 | .UseMySql(connection, new MySqlServerVersion(new Version())); 65 | //.UseMigrationNamespace(new MySqlMigrationNamespace());//迁移只会用connection string创建所以可以不加 66 | } 67 | if (tenantOptions.DbType == DbTypeEnum.MSSQL) 68 | { 69 | builder.UseSqlServer(connection); 70 | //.UseMigrationNamespace(new SqlServerMigrationNamespace()); 71 | } 72 | builder.UseLoggerFactory(efLogger) 73 | .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); 74 | }); 75 | o.AddDefaultDataSource(tenantOptions.DefaultDataSourceName,tenantOptions.DefaultConnectionString); 76 | //注意这个迁移必须要十分重要 77 | //注意这个迁移必须要十分重要 78 | //注意这个迁移必须要十分重要 79 | //注意这个迁移必须要十分重要 80 | o.UseShardingMigrationConfigure(b => 81 | { 82 | if (tenantOptions.DbType == DbTypeEnum.MYSQL) 83 | { 84 | b.ReplaceService(); 85 | } 86 | if (tenantOptions.DbType == DbTypeEnum.MSSQL) 87 | { 88 | b.ReplaceService(); 89 | } 90 | }); 91 | }).AddServiceConfigure(s => 92 | { 93 | //IShardingRuntimeContext内部的依赖注入 94 | s.AddSingleton(tenantOptions); 95 | }); 96 | 97 | if (tenantOptions.DbType == DbTypeEnum.MYSQL) 98 | { 99 | shardingRuntimeBuilder.ReplaceService(ServiceLifetime 100 | .Singleton); 101 | } 102 | if (tenantOptions.DbType == DbTypeEnum.MSSQL) 103 | { 104 | shardingRuntimeBuilder.ReplaceService(ServiceLifetime 105 | .Singleton); 106 | } 107 | return shardingRuntimeBuilder.Build(_serviceProvider); 108 | } 109 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | # TodoApp 255 | src/TodoApp.Web/Logs/* 256 | src/TodoApp.Web.Host/Logs/* 257 | src/TodoApp.IdentityServer/Logs/* 258 | src/TodoApp.HttpApi.Host/Logs/* 259 | src/TodoApp.HttpApi.HostWithIds/Logs/* 260 | src/TodoApp.DbMigrator/Logs/* 261 | -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/Controllers/IdentitySys/PassportController.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.IdentityModel.Tokens.Jwt; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.IdentityModel.Tokens; 9 | using Newtonsoft.Json; 10 | using ShardingCore.Extensions; 11 | using ShardingCore.Helpers; 12 | using ShardingCoreMultiTenantSys.DbContexts; 13 | using ShardingCoreMultiTenantSys.IdentitySys; 14 | using ShardingCoreMultiTenantSys.IdentitySys.Domain.Entities; 15 | using ShardingCoreMultiTenantSys.IdentitySys.ShardingConfigs; 16 | using ShardingCoreMultiTenantSys.Tenants; 17 | 18 | namespace ShardingCoreMultiTenantSys.Controllers.IdentitySys 19 | { 20 | [Route("api/[controller]/[action]")] 21 | [ApiController] 22 | [AllowAnonymous] 23 | public class PassportController : ControllerBase 24 | { 25 | private readonly IServiceProvider _serviceProvider; 26 | private readonly IdentityDbContext _identityDbContext; 27 | private readonly ITenantManager _tenantManager; 28 | private readonly IShardingBuilder _shardingBuilder; 29 | 30 | public PassportController(IServiceProvider serviceProvider, IdentityDbContext identityDbContext, 31 | ITenantManager tenantManager, IShardingBuilder shardingBuilder) 32 | { 33 | _serviceProvider = serviceProvider; 34 | _identityDbContext = identityDbContext; 35 | _tenantManager = tenantManager; 36 | _shardingBuilder = shardingBuilder; 37 | } 38 | 39 | [HttpPost] 40 | public async Task Register(RegisterRequest request) 41 | { 42 | if (await _identityDbContext.Set().AnyAsync(o => o.Name == request.Name)) 43 | return BadRequest("user not exists"); 44 | var sysUser = new SysUser() 45 | { 46 | Id = Guid.NewGuid().ToString("n"), 47 | Name = request.Name, 48 | Password = request.Password, 49 | CreationTime = DateTime.Now 50 | }; 51 | var shardingTenantOptions = new ShardingTenantOptions() 52 | { 53 | DbType = request.DbType, 54 | OrderShardingType = request.OrderShardingType, 55 | BeginTimeForSharding = request.BeginTimeForSharding.Value, 56 | DefaultDataSourceName = "ds0", 57 | DefaultConnectionString = GetDefaultString(request.DbType, sysUser.Id), 58 | MigrationNamespace = request.MigrationNamespace 59 | }; 60 | var sysUserTenantConfig = new SysUserTenantConfig() 61 | { 62 | Id = Guid.NewGuid().ToString("n"), 63 | UserId = sysUser.Id, 64 | CreationTime = DateTime.Now, 65 | ConfigJson = JsonConvert.SerializeObject(shardingTenantOptions) 66 | }; 67 | await _identityDbContext.AddAsync(sysUser); 68 | await _identityDbContext.AddAsync(sysUserTenantConfig); 69 | await _identityDbContext.SaveChangesAsync(); 70 | var shardingRuntimeContext = _shardingBuilder.Build(shardingTenantOptions); 71 | _tenantManager.AddTenantSharding(sysUser.Id, shardingRuntimeContext); 72 | using (_tenantManager.CreateScope(sysUser.Id)) 73 | using (var scope = _serviceProvider.CreateScope()) 74 | { 75 | var runtimeContext = _tenantManager.GetCurrentTenantContext().GetShardingRuntimeContext(); 76 | runtimeContext.UseAutoShardingCreate(); //启动定时任务 77 | var tenantDbContext = scope.ServiceProvider.GetService(); 78 | tenantDbContext.Database.Migrate(); 79 | runtimeContext.UseAutoTryCompensateTable(); 80 | } 81 | 82 | return Ok(); 83 | } 84 | 85 | [HttpPost] 86 | public async Task Login(LoginRequest request) 87 | { 88 | var sysUser = await _identityDbContext.Set() 89 | .FirstOrDefaultAsync(o => o.Name == request.Name && o.Password == request.Password); 90 | if (sysUser == null) 91 | return BadRequest("name or password error"); 92 | 93 | //秘钥,就是标头,这里用Hmacsha256算法,需要256bit的密钥 94 | var securityKey = 95 | new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes("123123!@#!@#123123")), 96 | SecurityAlgorithms.HmacSha256); 97 | //Claim,JwtRegisteredClaimNames中预定义了好多种默认的参数名,也可以像下面的Guid一样自己定义键名. 98 | //ClaimTypes也预定义了好多类型如role、email、name。Role用于赋予权限,不同的角色可以访问不同的接口 99 | //相当于有效载荷 100 | var claims = new Claim[] 101 | { 102 | new Claim(JwtRegisteredClaimNames.Iss, "https://localhost:5000"), 103 | new Claim(JwtRegisteredClaimNames.Aud, "api"), 104 | new Claim("id", Guid.NewGuid().ToString("n")), 105 | new Claim("uid", sysUser.Id), 106 | }; 107 | SecurityToken securityToken = new JwtSecurityToken( 108 | signingCredentials: securityKey, 109 | expires: DateTime.Now.AddHours(2), //过期时间 110 | claims: claims 111 | ); 112 | var token = new JwtSecurityTokenHandler().WriteToken(securityToken); 113 | return Ok(token); 114 | } 115 | 116 | private string GetDefaultString(DbTypeEnum dbType, string userId) 117 | { 118 | switch (dbType) 119 | { 120 | case DbTypeEnum.MSSQL: 121 | return $"Data Source=localhost;Initial Catalog=DB{userId};Integrated Security=True;"; 122 | case DbTypeEnum.MYSQL: 123 | return $"server=127.0.0.1;port=3306;database=DB{userId};userid=root;password=L6yBtV6qNENrwBy7;"; 124 | default: throw new NotImplementedException(); 125 | } 126 | } 127 | } 128 | 129 | public class RegisterRequest 130 | { 131 | public string Name { get; set; } 132 | public string Password { get; set; } 133 | public DbTypeEnum DbType { get; set; } 134 | public OrderShardingTypeEnum OrderShardingType { get; set; } 135 | public DateTime? BeginTimeForSharding { get; set; } 136 | /// 137 | /// 分片迁移的命名空间 138 | /// 139 | public string MigrationNamespace { get; set; } 140 | } 141 | 142 | public class LoginRequest 143 | { 144 | public string Name { get; set; } 145 | public string Password { get; set; } 146 | } 147 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/MultiDatabaseMigrationsAssembly.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Diagnostics; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | 7 | namespace ShardingCoreMultiTenantSys.MigrationsAssemblies; 8 | 9 | public class MultiDatabaseMigrationsAssembly: IMigrationsAssembly 10 | { 11 | public string MigrationNamespace { get; } 12 | private readonly IMigrationsIdGenerator _idGenerator; 13 | private readonly IDiagnosticsLogger _logger; 14 | private IReadOnlyDictionary? _migrations; 15 | private ModelSnapshot? _modelSnapshot; 16 | private readonly Type _contextType; 17 | 18 | /// 19 | /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 20 | /// the same compatibility standards as public APIs. It may be changed or removed without notice in 21 | /// any release. You should only use it directly in your code with extreme caution and knowing that 22 | /// doing so can result in application failures when updating to a new Entity Framework Core release. 23 | /// 24 | public MultiDatabaseMigrationsAssembly( 25 | IMigrationNamespace migrationNamespace, 26 | ICurrentDbContext currentContext, 27 | IDbContextOptions options, 28 | IMigrationsIdGenerator idGenerator, 29 | IDiagnosticsLogger logger) 30 | { 31 | 32 | _contextType = currentContext.Context.GetType(); 33 | 34 | var assemblyName = RelationalOptionsExtension.Extract(options)?.MigrationsAssembly; 35 | Assembly = assemblyName == null 36 | ? _contextType.Assembly 37 | : Assembly.Load(new AssemblyName(assemblyName)); 38 | 39 | MigrationNamespace = migrationNamespace.GetNamespace(); 40 | _idGenerator = idGenerator; 41 | _logger = logger; 42 | } 43 | 44 | /// 45 | /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 46 | /// the same compatibility standards as public APIs. It may be changed or removed without notice in 47 | /// any release. You should only use it directly in your code with extreme caution and knowing that 48 | /// doing so can result in application failures when updating to a new Entity Framework Core release. 49 | /// 50 | public virtual IReadOnlyDictionary Migrations 51 | { 52 | get 53 | { 54 | IReadOnlyDictionary Create() 55 | { 56 | var result = new SortedList(); 57 | var items 58 | = from t in Assembly.GetConstructibleTypes() 59 | where t.IsSubclassOf(typeof(Migration)) 60 | && t.Namespace.Equals(MigrationNamespace) 61 | && t.GetCustomAttribute()?.ContextType == _contextType 62 | let id = t.GetCustomAttribute()?.Id 63 | orderby id 64 | select (id, t); 65 | foreach (var (id, t) in items) 66 | { 67 | if (id == null) 68 | { 69 | _logger.MigrationAttributeMissingWarning(t); 70 | 71 | continue; 72 | } 73 | 74 | result.Add(id, t); 75 | } 76 | 77 | return result; 78 | } 79 | 80 | return _migrations ??= Create(); 81 | } 82 | } 83 | 84 | /// 85 | /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 86 | /// the same compatibility standards as public APIs. It may be changed or removed without notice in 87 | /// any release. You should only use it directly in your code with extreme caution and knowing that 88 | /// doing so can result in application failures when updating to a new Entity Framework Core release. 89 | /// 90 | public virtual ModelSnapshot? ModelSnapshot 91 | => _modelSnapshot ??= (from t in Assembly.GetConstructibleTypes() 92 | where t.IsSubclassOf(typeof(ModelSnapshot)) 93 | && MigrationNamespace.Equals(t?.Namespace) 94 | && t.GetCustomAttribute()?.ContextType == _contextType 95 | select (ModelSnapshot)Activator.CreateInstance(t.AsType())!) 96 | .FirstOrDefault(); 97 | 98 | /// 99 | /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 100 | /// the same compatibility standards as public APIs. It may be changed or removed without notice in 101 | /// any release. You should only use it directly in your code with extreme caution and knowing that 102 | /// doing so can result in application failures when updating to a new Entity Framework Core release. 103 | /// 104 | public virtual Assembly Assembly { get; } 105 | 106 | /// 107 | /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 108 | /// the same compatibility standards as public APIs. It may be changed or removed without notice in 109 | /// any release. You should only use it directly in your code with extreme caution and knowing that 110 | /// doing so can result in application failures when updating to a new Entity Framework Core release. 111 | /// 112 | public virtual string? FindMigrationId(string nameOrId) 113 | => Migrations.Keys 114 | .Where( 115 | _idGenerator.IsValidId(nameOrId) 116 | // ReSharper disable once ImplicitlyCapturedClosure 117 | ? id => string.Equals(id, nameOrId, StringComparison.OrdinalIgnoreCase) 118 | : id => string.Equals(_idGenerator.GetName(id), nameOrId, StringComparison.OrdinalIgnoreCase)) 119 | .FirstOrDefault(); 120 | 121 | /// 122 | /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 123 | /// the same compatibility standards as public APIs. It may be changed or removed without notice in 124 | /// any release. You should only use it directly in your code with extreme caution and knowing that 125 | /// doing so can result in application failures when updating to a new Entity Framework Core release. 126 | /// 127 | public virtual Migration CreateMigration(TypeInfo migrationClass, string activeProvider) 128 | { 129 | Console.WriteLine(migrationClass.FullName); 130 | 131 | var migration = (Migration)Activator.CreateInstance(migrationClass.AsType())!; 132 | migration.ActiveProvider = activeProvider; 133 | 134 | return migration; 135 | } 136 | } -------------------------------------------------------------------------------- /ShardingCoreMultiTenantSys/MigrationsAssemblies/SharedTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | 8 | #nullable enable 9 | 10 | // ReSharper disable once CheckNamespace 11 | namespace System 12 | { 13 | [DebuggerStepThrough] 14 | internal static class SharedTypeExtensions 15 | { 16 | private static readonly Dictionary _builtInTypeNames = new() 17 | { 18 | { typeof(bool), "bool" }, 19 | { typeof(byte), "byte" }, 20 | { typeof(char), "char" }, 21 | { typeof(decimal), "decimal" }, 22 | { typeof(double), "double" }, 23 | { typeof(float), "float" }, 24 | { typeof(int), "int" }, 25 | { typeof(long), "long" }, 26 | { typeof(object), "object" }, 27 | { typeof(sbyte), "sbyte" }, 28 | { typeof(short), "short" }, 29 | { typeof(string), "string" }, 30 | { typeof(uint), "uint" }, 31 | { typeof(ulong), "ulong" }, 32 | { typeof(ushort), "ushort" }, 33 | { typeof(void), "void" } 34 | }; 35 | 36 | public static Type UnwrapNullableType(this Type type) 37 | => Nullable.GetUnderlyingType(type) ?? type; 38 | 39 | public static bool IsNullableValueType(this Type type) 40 | => type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 41 | 42 | public static bool IsNullableType(this Type type) 43 | => !type.IsValueType || type.IsNullableValueType(); 44 | 45 | public static bool IsValidEntityType(this Type type) 46 | => type.IsClass 47 | && !type.IsArray; 48 | 49 | public static bool IsPropertyBagType(this Type type) 50 | { 51 | if (type.IsGenericTypeDefinition) 52 | { 53 | return false; 54 | } 55 | 56 | var types = GetGenericTypeImplementations(type, typeof(IDictionary<,>)); 57 | return types.Any( 58 | t => t.GetGenericArguments()[0] == typeof(string) 59 | && t.GetGenericArguments()[1] == typeof(object)); 60 | } 61 | 62 | public static Type MakeNullable(this Type type, bool nullable = true) 63 | => type.IsNullableType() == nullable 64 | ? type 65 | : nullable 66 | ? typeof(Nullable<>).MakeGenericType(type) 67 | : type.UnwrapNullableType(); 68 | 69 | public static bool IsNumeric(this Type type) 70 | { 71 | type = type.UnwrapNullableType(); 72 | 73 | return type.IsInteger() 74 | || type == typeof(decimal) 75 | || type == typeof(float) 76 | || type == typeof(double); 77 | } 78 | 79 | public static bool IsInteger(this Type type) 80 | { 81 | type = type.UnwrapNullableType(); 82 | 83 | return type == typeof(int) 84 | || type == typeof(long) 85 | || type == typeof(short) 86 | || type == typeof(byte) 87 | || type == typeof(uint) 88 | || type == typeof(ulong) 89 | || type == typeof(ushort) 90 | || type == typeof(sbyte) 91 | || type == typeof(char); 92 | } 93 | 94 | public static bool IsSignedInteger(this Type type) 95 | => type == typeof(int) 96 | || type == typeof(long) 97 | || type == typeof(short) 98 | || type == typeof(sbyte); 99 | 100 | public static bool IsAnonymousType(this Type type) 101 | => type.Name.StartsWith("<>", StringComparison.Ordinal) 102 | && type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), inherit: false).Length > 0 103 | && type.Name.Contains("AnonymousType"); 104 | 105 | public static bool IsTupleType(this Type type) 106 | { 107 | if (type == typeof(Tuple)) 108 | { 109 | return true; 110 | } 111 | 112 | if (type.IsGenericType) 113 | { 114 | var genericDefinition = type.GetGenericTypeDefinition(); 115 | if (genericDefinition == typeof(Tuple<>) 116 | || genericDefinition == typeof(Tuple<,>) 117 | || genericDefinition == typeof(Tuple<,,>) 118 | || genericDefinition == typeof(Tuple<,,,>) 119 | || genericDefinition == typeof(Tuple<,,,,>) 120 | || genericDefinition == typeof(Tuple<,,,,,>) 121 | || genericDefinition == typeof(Tuple<,,,,,,>) 122 | || genericDefinition == typeof(Tuple<,,,,,,,>) 123 | || genericDefinition == typeof(Tuple<,,,,,,,>)) 124 | { 125 | return true; 126 | } 127 | } 128 | 129 | return false; 130 | } 131 | 132 | public static PropertyInfo? GetAnyProperty(this Type type, string name) 133 | { 134 | var props = type.GetRuntimeProperties().Where(p => p.Name == name).ToList(); 135 | if (props.Count > 1) 136 | { 137 | throw new AmbiguousMatchException(); 138 | } 139 | 140 | return props.SingleOrDefault(); 141 | } 142 | 143 | public static MethodInfo GetRequiredMethod(this Type type, string name, params Type[] parameters) 144 | { 145 | var method = type.GetTypeInfo().GetMethod(name, parameters); 146 | 147 | if (method == null 148 | && parameters.Length == 0) 149 | { 150 | method = type.GetMethod(name); 151 | } 152 | 153 | if (method == null) 154 | { 155 | throw new InvalidOperationException(); 156 | } 157 | 158 | return method; 159 | } 160 | 161 | public static PropertyInfo GetRequiredProperty(this Type type, string name) 162 | => type.GetTypeInfo().GetProperty(name) 163 | ?? throw new InvalidOperationException($"Could not find property '{name}' on type '{type}'"); 164 | 165 | public static FieldInfo GetRequiredDeclaredField(this Type type, string name) 166 | => type.GetTypeInfo().GetDeclaredField(name) 167 | ?? throw new InvalidOperationException($"Could not find field '{name}' on type '{type}'"); 168 | 169 | public static MethodInfo GetRequiredDeclaredMethod(this Type type, string name) 170 | => type.GetTypeInfo().GetDeclaredMethod(name) 171 | ?? throw new InvalidOperationException($"Could not find method '{name}' on type '{type}'"); 172 | 173 | public static MethodInfo GetRequiredDeclaredMethod(this Type type, string name, Func methodSelector) 174 | => type.GetTypeInfo().GetDeclaredMethods(name).Single(methodSelector); 175 | 176 | public static PropertyInfo GetRequiredDeclaredProperty(this Type type, string name) 177 | => type.GetTypeInfo().GetDeclaredProperty(name) 178 | ?? throw new InvalidOperationException($"Could not find property '{name}' on type '{type}'"); 179 | 180 | public static MethodInfo GetRequiredRuntimeMethod(this Type type, string name, params Type[] parameters) 181 | => type.GetTypeInfo().GetRuntimeMethod(name, parameters) 182 | ?? throw new InvalidOperationException($"Could not find method '{name}' on type '{type}'"); 183 | 184 | public static PropertyInfo GetRequiredRuntimeProperty(this Type type, string name) 185 | => type.GetTypeInfo().GetRuntimeProperty(name) 186 | ?? throw new InvalidOperationException($"Could not find property '{name}' on type '{type}'"); 187 | 188 | public static bool IsInstantiable(this Type type) 189 | => !type.IsAbstract 190 | && !type.IsInterface 191 | && (!type.IsGenericType || !type.IsGenericTypeDefinition); 192 | 193 | public static Type UnwrapEnumType(this Type type) 194 | { 195 | var isNullable = type.IsNullableType(); 196 | var underlyingNonNullableType = isNullable ? type.UnwrapNullableType() : type; 197 | if (!underlyingNonNullableType.IsEnum) 198 | { 199 | return type; 200 | } 201 | 202 | var underlyingEnumType = Enum.GetUnderlyingType(underlyingNonNullableType); 203 | return isNullable ? MakeNullable(underlyingEnumType) : underlyingEnumType; 204 | } 205 | 206 | public static Type GetSequenceType(this Type type) 207 | { 208 | var sequenceType = TryGetSequenceType(type); 209 | if (sequenceType == null) 210 | { 211 | throw new ArgumentException($"The type {type.Name} does not represent a sequence"); 212 | } 213 | 214 | return sequenceType; 215 | } 216 | 217 | public static Type? TryGetSequenceType(this Type type) 218 | => type.TryGetElementType(typeof(IEnumerable<>)) 219 | ?? type.TryGetElementType(typeof(IAsyncEnumerable<>)); 220 | 221 | public static Type? TryGetElementType(this Type type, Type interfaceOrBaseType) 222 | { 223 | if (type.IsGenericTypeDefinition) 224 | { 225 | return null; 226 | } 227 | 228 | var types = GetGenericTypeImplementations(type, interfaceOrBaseType); 229 | 230 | Type? singleImplementation = null; 231 | foreach (var implementation in types) 232 | { 233 | if (singleImplementation == null) 234 | { 235 | singleImplementation = implementation; 236 | } 237 | else 238 | { 239 | singleImplementation = null; 240 | break; 241 | } 242 | } 243 | 244 | return singleImplementation?.GenericTypeArguments.FirstOrDefault(); 245 | } 246 | 247 | public static bool IsCompatibleWith(this Type propertyType, Type fieldType) 248 | { 249 | if (propertyType.IsAssignableFrom(fieldType) 250 | || fieldType.IsAssignableFrom(propertyType)) 251 | { 252 | return true; 253 | } 254 | 255 | var propertyElementType = propertyType.TryGetSequenceType(); 256 | var fieldElementType = fieldType.TryGetSequenceType(); 257 | 258 | return propertyElementType != null 259 | && fieldElementType != null 260 | && IsCompatibleWith(propertyElementType, fieldElementType); 261 | } 262 | 263 | public static IEnumerable GetGenericTypeImplementations(this Type type, Type interfaceOrBaseType) 264 | { 265 | var typeInfo = type.GetTypeInfo(); 266 | if (!typeInfo.IsGenericTypeDefinition) 267 | { 268 | var baseTypes = interfaceOrBaseType.GetTypeInfo().IsInterface 269 | ? typeInfo.ImplementedInterfaces 270 | : type.GetBaseTypes(); 271 | foreach (var baseType in baseTypes) 272 | { 273 | if (baseType.IsGenericType 274 | && baseType.GetGenericTypeDefinition() == interfaceOrBaseType) 275 | { 276 | yield return baseType; 277 | } 278 | } 279 | 280 | if (type.IsGenericType 281 | && type.GetGenericTypeDefinition() == interfaceOrBaseType) 282 | { 283 | yield return type; 284 | } 285 | } 286 | } 287 | 288 | public static IEnumerable GetBaseTypes(this Type type) 289 | { 290 | var currentType = type.BaseType; 291 | 292 | while (currentType != null) 293 | { 294 | yield return currentType; 295 | 296 | currentType = currentType.BaseType; 297 | } 298 | } 299 | 300 | public static List GetBaseTypesAndInterfacesInclusive(this Type type) 301 | { 302 | var baseTypes = new List(); 303 | var typesToProcess = new Queue(); 304 | typesToProcess.Enqueue(type); 305 | 306 | while (typesToProcess.Count > 0) 307 | { 308 | type = typesToProcess.Dequeue(); 309 | baseTypes.Add(type); 310 | 311 | if (type.IsNullableValueType()) 312 | { 313 | typesToProcess.Enqueue(Nullable.GetUnderlyingType(type)!); 314 | } 315 | 316 | if (type.IsConstructedGenericType) 317 | { 318 | typesToProcess.Enqueue(type.GetGenericTypeDefinition()); 319 | } 320 | 321 | if (!type.IsGenericTypeDefinition 322 | && !type.IsInterface) 323 | { 324 | if (type.BaseType != null) 325 | { 326 | typesToProcess.Enqueue(type.BaseType); 327 | } 328 | 329 | foreach (var @interface in GetDeclaredInterfaces(type)) 330 | { 331 | typesToProcess.Enqueue(@interface); 332 | } 333 | } 334 | } 335 | 336 | return baseTypes; 337 | } 338 | 339 | public static IEnumerable GetTypesInHierarchy(this Type type) 340 | { 341 | var currentType = type; 342 | 343 | while (currentType != null) 344 | { 345 | yield return currentType; 346 | 347 | currentType = currentType.BaseType; 348 | } 349 | } 350 | 351 | public static IEnumerable GetDeclaredInterfaces(this Type type) 352 | { 353 | var interfaces = type.GetInterfaces(); 354 | if (type.BaseType == typeof(object) 355 | || type.BaseType == null) 356 | { 357 | return interfaces; 358 | } 359 | 360 | return interfaces.Except(type.BaseType.GetInterfaces()); 361 | } 362 | 363 | public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[]? types) 364 | { 365 | types ??= Array.Empty(); 366 | 367 | return type.GetTypeInfo().DeclaredConstructors 368 | .SingleOrDefault( 369 | c => !c.IsStatic 370 | && c.GetParameters().Select(p => p.ParameterType).SequenceEqual(types))!; 371 | } 372 | 373 | public static IEnumerable GetPropertiesInHierarchy(this Type type, string name) 374 | { 375 | var currentType = type; 376 | do 377 | { 378 | var typeInfo = currentType.GetTypeInfo(); 379 | foreach (var propertyInfo in typeInfo.DeclaredProperties) 380 | { 381 | if (propertyInfo.Name.Equals(name, StringComparison.Ordinal) 382 | && !(propertyInfo.GetMethod ?? propertyInfo.SetMethod)!.IsStatic) 383 | { 384 | yield return propertyInfo; 385 | } 386 | } 387 | 388 | currentType = typeInfo.BaseType; 389 | } 390 | while (currentType != null); 391 | } 392 | 393 | // Looking up the members through the whole hierarchy allows to find inherited private members. 394 | public static IEnumerable GetMembersInHierarchy(this Type type) 395 | { 396 | var currentType = type; 397 | 398 | do 399 | { 400 | // Do the whole hierarchy for properties first since looking for fields is slower. 401 | foreach (var propertyInfo in currentType.GetRuntimeProperties().Where(pi => !(pi.GetMethod ?? pi.SetMethod)!.IsStatic)) 402 | { 403 | yield return propertyInfo; 404 | } 405 | 406 | foreach (var fieldInfo in currentType.GetRuntimeFields().Where(f => !f.IsStatic)) 407 | { 408 | yield return fieldInfo; 409 | } 410 | 411 | currentType = currentType.BaseType; 412 | } 413 | while (currentType != null); 414 | } 415 | 416 | public static IEnumerable GetMembersInHierarchy(this Type type, string name) 417 | => type.GetMembersInHierarchy().Where(m => m.Name == name); 418 | 419 | private static readonly Dictionary _commonTypeDictionary = new() 420 | { 421 | #pragma warning disable IDE0034 // Simplify 'default' expression - default causes default(object) 422 | { typeof(int), default(int) }, 423 | { typeof(Guid), default(Guid) }, 424 | { typeof(DateOnly), default(DateOnly) }, 425 | { typeof(DateTime), default(DateTime) }, 426 | { typeof(DateTimeOffset), default(DateTimeOffset) }, 427 | { typeof(TimeOnly), default(TimeOnly) }, 428 | { typeof(long), default(long) }, 429 | { typeof(bool), default(bool) }, 430 | { typeof(double), default(double) }, 431 | { typeof(short), default(short) }, 432 | { typeof(float), default(float) }, 433 | { typeof(byte), default(byte) }, 434 | { typeof(char), default(char) }, 435 | { typeof(uint), default(uint) }, 436 | { typeof(ushort), default(ushort) }, 437 | { typeof(ulong), default(ulong) }, 438 | { typeof(sbyte), default(sbyte) } 439 | #pragma warning restore IDE0034 // Simplify 'default' expression 440 | }; 441 | 442 | public static object? GetDefaultValue(this Type type) 443 | { 444 | if (!type.IsValueType) 445 | { 446 | return null; 447 | } 448 | 449 | // A bit of perf code to avoid calling Activator.CreateInstance for common types and 450 | // to avoid boxing on every call. This is about 50% faster than just calling CreateInstance 451 | // for all value types. 452 | return _commonTypeDictionary.TryGetValue(type, out var value) 453 | ? value 454 | : Activator.CreateInstance(type); 455 | } 456 | 457 | public static IEnumerable GetConstructibleTypes(this Assembly assembly) 458 | => assembly.GetLoadableDefinedTypes().Where( 459 | t => !t.IsAbstract 460 | && !t.IsGenericTypeDefinition); 461 | 462 | public static IEnumerable GetLoadableDefinedTypes(this Assembly assembly) 463 | { 464 | try 465 | { 466 | return assembly.DefinedTypes; 467 | } 468 | catch (ReflectionTypeLoadException ex) 469 | { 470 | return ex.Types.Where(t => t != null).Select(IntrospectionExtensions.GetTypeInfo!); 471 | } 472 | } 473 | 474 | public static bool IsQueryableType(this Type type) 475 | { 476 | if (type.IsGenericType 477 | && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) 478 | { 479 | return true; 480 | } 481 | 482 | return type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQueryable<>)); 483 | } 484 | 485 | /// 486 | /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 487 | /// the same compatibility standards as public APIs. It may be changed or removed without notice in 488 | /// any release. You should only use it directly in your code with extreme caution and knowing that 489 | /// doing so can result in application failures when updating to a new Entity Framework Core release. 490 | /// 491 | public static string DisplayName(this Type type, bool fullName = true, bool compilable = false) 492 | { 493 | var stringBuilder = new StringBuilder(); 494 | ProcessType(stringBuilder, type, fullName, compilable); 495 | return stringBuilder.ToString(); 496 | } 497 | 498 | private static void ProcessType(StringBuilder builder, Type type, bool fullName, bool compilable) 499 | { 500 | if (type.IsGenericType) 501 | { 502 | var genericArguments = type.GetGenericArguments(); 503 | ProcessGenericType(builder, type, genericArguments, genericArguments.Length, fullName, compilable); 504 | } 505 | else if (type.IsArray) 506 | { 507 | ProcessArrayType(builder, type, fullName, compilable); 508 | } 509 | else if (_builtInTypeNames.TryGetValue(type, out var builtInName)) 510 | { 511 | builder.Append(builtInName); 512 | } 513 | else if (!type.IsGenericParameter) 514 | { 515 | if (compilable) 516 | { 517 | if (type.IsNested) 518 | { 519 | ProcessType(builder, type.DeclaringType!, fullName, compilable); 520 | builder.Append('.'); 521 | } 522 | else if (fullName) 523 | { 524 | builder.Append(type.Namespace).Append('.'); 525 | } 526 | 527 | builder.Append(type.Name); 528 | } 529 | else 530 | { 531 | builder.Append(fullName ? type.FullName : type.Name); 532 | } 533 | } 534 | } 535 | 536 | private static void ProcessArrayType(StringBuilder builder, Type type, bool fullName, bool compilable) 537 | { 538 | var innerType = type; 539 | while (innerType.IsArray) 540 | { 541 | innerType = innerType.GetElementType()!; 542 | } 543 | 544 | ProcessType(builder, innerType, fullName, compilable); 545 | 546 | while (type.IsArray) 547 | { 548 | builder.Append('['); 549 | builder.Append(',', type.GetArrayRank() - 1); 550 | builder.Append(']'); 551 | type = type.GetElementType()!; 552 | } 553 | } 554 | 555 | private static void ProcessGenericType( 556 | StringBuilder builder, 557 | Type type, 558 | Type[] genericArguments, 559 | int length, 560 | bool fullName, 561 | bool compilable) 562 | { 563 | if (type.IsConstructedGenericType 564 | && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 565 | { 566 | ProcessType(builder, type.UnwrapNullableType(), fullName, compilable); 567 | builder.Append('?'); 568 | return; 569 | } 570 | 571 | var offset = type.IsNested ? type.DeclaringType!.GetGenericArguments().Length : 0; 572 | 573 | if (compilable) 574 | { 575 | if (type.IsNested) 576 | { 577 | ProcessType(builder, type.DeclaringType!, fullName, compilable); 578 | builder.Append('.'); 579 | } 580 | else if (fullName) 581 | { 582 | builder.Append(type.Namespace); 583 | builder.Append('.'); 584 | } 585 | } 586 | else 587 | { 588 | if (fullName) 589 | { 590 | if (type.IsNested) 591 | { 592 | ProcessGenericType(builder, type.DeclaringType!, genericArguments, offset, fullName, compilable); 593 | builder.Append('+'); 594 | } 595 | else 596 | { 597 | builder.Append(type.Namespace); 598 | builder.Append('.'); 599 | } 600 | } 601 | } 602 | 603 | var genericPartIndex = type.Name.IndexOf('`'); 604 | if (genericPartIndex <= 0) 605 | { 606 | builder.Append(type.Name); 607 | return; 608 | } 609 | 610 | builder.Append(type.Name, 0, genericPartIndex); 611 | builder.Append('<'); 612 | 613 | for (var i = offset; i < length; i++) 614 | { 615 | ProcessType(builder, genericArguments[i], fullName, compilable); 616 | if (i + 1 == length) 617 | { 618 | continue; 619 | } 620 | 621 | builder.Append(','); 622 | if (!genericArguments[i + 1].IsGenericParameter) 623 | { 624 | builder.Append(' '); 625 | } 626 | } 627 | 628 | builder.Append('>'); 629 | } 630 | 631 | public static IEnumerable GetNamespaces(this Type type) 632 | { 633 | if (_builtInTypeNames.ContainsKey(type)) 634 | { 635 | yield break; 636 | } 637 | 638 | yield return type.Namespace!; 639 | 640 | if (type.IsGenericType) 641 | { 642 | foreach (var typeArgument in type.GenericTypeArguments) 643 | { 644 | foreach (var ns in typeArgument.GetNamespaces()) 645 | { 646 | yield return ns; 647 | } 648 | } 649 | } 650 | } 651 | 652 | public static ConstantExpression GetDefaultValueConstant(this Type type) 653 | => (ConstantExpression)_generateDefaultValueConstantMethod 654 | .MakeGenericMethod(type).Invoke(null, Array.Empty())!; 655 | 656 | private static readonly MethodInfo _generateDefaultValueConstantMethod = 657 | typeof(SharedTypeExtensions).GetTypeInfo().GetDeclaredMethod(nameof(GenerateDefaultValueConstant))!; 658 | 659 | private static ConstantExpression GenerateDefaultValueConstant() 660 | => Expression.Constant(default(TDefault), typeof(TDefault)); 661 | } 662 | } 663 | --------------------------------------------------------------------------------