diff --git a/examples/Demo.DateSharding/Program.cs b/examples/Demo.DateSharding/Program.cs index 1bf98cb..4c36f2e 100644 --- a/examples/Demo.DateSharding/Program.cs +++ b/examples/Demo.DateSharding/Program.cs @@ -1,6 +1,7 @@ using EFCore.Sharding; using EFCore.Sharding.Tests; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; @@ -11,55 +12,54 @@ class Program static async Task Main(string[] args) { DateTime startTime = DateTime.Now.AddMinutes(-5); - var host = Host.CreateDefaultBuilder() - .ConfigureServices(services => - { - //配置初始化 - services.AddEFCoreSharding(config => - { - //添加数据源 - config.AddDataSource(Config.CONSTRING1, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); + ServiceCollection services = new ServiceCollection(); + //配置初始化 + services.AddLogging(x => + { + x.AddConsole(); + }); + services.AddEFCoreSharding(config => + { + //添加数据源 + config.AddDataSource(Config.CONSTRING1, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); - //按分钟分表 - config.SetDateSharding(nameof(Base_UnitTest.CreateTime), ExpandByDateMode.PerMinute, startTime); - }); - }).Build(); - //host.Start(); + //按分钟分表 + config.SetDateSharding(nameof(Base_UnitTest.CreateTime), ExpandByDateMode.PerMinute, startTime); + }); - //var serviceProvider = host.Services; + var serviceProvider = services.BuildServiceProvider(); + new EFCoreShardingBootstrapper(serviceProvider).StartAsync(default).Wait(); - //using var scop = serviceProvider.CreateScope(); + using var scop = serviceProvider.CreateScope(); - //var db = scop.ServiceProvider.GetService(); - //var logger = scop.ServiceProvider.GetService>(); + var db = scop.ServiceProvider.GetService(); + var logger = scop.ServiceProvider.GetService>(); - //await Task.CompletedTask; - //Console.ReadLine(); - //while (true) - //{ - // try - // { - // await db.InsertAsync(new Base_UnitTest - // { - // Id = Guid.NewGuid().ToString(), - // Age = 1, - // UserName = Guid.NewGuid().ToString(), - // CreateTime = DateTime.Now - // }); + while (true) + { + try + { + await db.InsertAsync(new Base_UnitTest + { + Id = Guid.NewGuid().ToString(), + Age = 1, + UserName = Guid.NewGuid().ToString(), + CreateTime = DateTime.Now + }); - // DateTime time = DateTime.Now.AddMinutes(-2); - // var count = await db.GetIShardingQueryable() - // .Where(x => x.CreateTime >= time) - // .CountAsync(); - // logger.LogWarning("当前数据量:{Count}", count); - // } - // catch (Exception ex) - // { - // Console.WriteLine(ex.Message); - // } + DateTime time = DateTime.Now.AddMinutes(-2); + var count = await db.GetIShardingQueryable() + .Where(x => x.CreateTime >= time) + .CountAsync(); + logger.LogWarning("当前数据量:{Count}", count); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } - // await Task.Delay(1000); - //} + await Task.Delay(1000); + } } } } diff --git a/examples/Demo.DbMigrator/Demo.DbMigrator.csproj b/examples/Demo.DbMigrator/Demo.DbMigrator.csproj index 6256254..9784660 100644 --- a/examples/Demo.DbMigrator/Demo.DbMigrator.csproj +++ b/examples/Demo.DbMigrator/Demo.DbMigrator.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs b/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs index 00199ff..f1ebd03 100644 --- a/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs +++ b/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs @@ -11,16 +11,20 @@ public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory { - x.MigrationsWithoutForeignKey(); + //取消建表 + x.CreateShardingTableOnStarting(false); + //使用分表迁移 + x.EnableShardingMigration(true); + //添加数据源 x.AddDataSource(_connectionString, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); - //按分钟分表 - x.SetDateSharding(nameof(Order.CreateTime), ExpandByDateMode.PerMinute, startTime); + //按月分表 + x.SetDateSharding(nameof(Order.CreateTime), ExpandByDateMode.PerMonth, startTime); }); ServiceProvider = services.BuildServiceProvider(); new EFCoreShardingBootstrapper(ServiceProvider).StartAsync(default).Wait(); diff --git a/examples/Demo.DbMigrator/Entities/Order.cs b/examples/Demo.DbMigrator/Entities/Order.cs index 8187de6..5b042af 100644 --- a/examples/Demo.DbMigrator/Entities/Order.cs +++ b/examples/Demo.DbMigrator/Entities/Order.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -8,9 +7,31 @@ namespace Demo.DbMigrator [Table(nameof(Order))] public class Order { - [Key] + /// + /// 主键 + /// + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } + + /// + /// 创建时间 + /// public DateTime CreateTime { get; set; } - public List OrderItems { get; set; } = new List(); + + /// + /// 订单号 + /// + [StringLength(50)] + public string OrderNum { get; set; } + + /// + /// 订单名 + /// + public string Name { get; set; } + + /// + /// 商品数量 + /// + public int Count { get; set; } } } diff --git a/examples/Demo.DbMigrator/Entities/OrderItem.cs b/examples/Demo.DbMigrator/Entities/OrderItem.cs index bf648b5..9957ec2 100644 --- a/examples/Demo.DbMigrator/Entities/OrderItem.cs +++ b/examples/Demo.DbMigrator/Entities/OrderItem.cs @@ -7,9 +7,15 @@ namespace Demo.DbMigrator [Table(nameof(OrderItem))] public class OrderItem { + /// + /// 主键 + /// [Key] public Guid Id { get; set; } + + /// + /// 订单Id + /// public Guid OrderId { get; set; } - public string Name { get; set; } } } diff --git a/examples/Demo.DbMigrator/EntityTypeConfigurations/OrderConfiguration.cs b/examples/Demo.DbMigrator/EntityTypeConfigurations/OrderConfiguration.cs index dede16f..ddc1873 100644 --- a/examples/Demo.DbMigrator/EntityTypeConfigurations/OrderConfiguration.cs +++ b/examples/Demo.DbMigrator/EntityTypeConfigurations/OrderConfiguration.cs @@ -8,10 +8,8 @@ internal class OrderConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { - builder - .HasMany(x => x.OrderItems) - .WithOne() - .HasForeignKey(x => x.OrderId); + builder.HasIndex(x => x.OrderNum).IsUnique(); + builder.Property(x => x.Id).HasDefaultValueSql("newsequentialid()"); } } } diff --git a/examples/Demo.DbMigrator/Migrations/20200927141554_InitialCreate.Designer.cs b/examples/Demo.DbMigrator/Migrations/20200927141554_InitialCreate.Designer.cs deleted file mode 100644 index 7aee51a..0000000 --- a/examples/Demo.DbMigrator/Migrations/20200927141554_InitialCreate.Designer.cs +++ /dev/null @@ -1,68 +0,0 @@ -// -using System; -using Demo.DbMigrator; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Demo.DbMigrator.Migrations -{ - [DbContext(typeof(CustomContext))] - [Migration("20200927141554_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.8") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Demo.DbMigrator.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreateTime") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Order"); - }); - - modelBuilder.Entity("Demo.DbMigrator.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItem"); - }); - - modelBuilder.Entity("Demo.DbMigrator.OrderItem", b => - { - b.HasOne("Demo.DbMigrator.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/examples/Demo.DbMigrator/Migrations/20200927141554_InitialCreate.cs b/examples/Demo.DbMigrator/Migrations/20200927141554_InitialCreate.cs deleted file mode 100644 index 6cbcfa6..0000000 --- a/examples/Demo.DbMigrator/Migrations/20200927141554_InitialCreate.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Demo.DbMigrator.Migrations -{ - public partial class InitialCreate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Order_202009272210", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272210", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Order_202009272211", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272211", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Order_202009272212", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272212", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Order_202009272213", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272213", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Order_202009272214", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272214", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Order_202009272215", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272215", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Order_202009272216", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272216", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Order_202009272217", - columns: table => new - { - Id = table.Column(nullable: false), - CreateTime = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Order_202009272217", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "OrderItem", - columns: table => new - { - Id = table.Column(nullable: false), - OrderId = table.Column(nullable: false), - Name = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItem", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_OrderId", - table: "OrderItem", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "OrderItem"); - - migrationBuilder.DropTable( - name: "Order_202009272210"); - - migrationBuilder.DropTable( - name: "Order_202009272211"); - - migrationBuilder.DropTable( - name: "Order_202009272212"); - - migrationBuilder.DropTable( - name: "Order_202009272213"); - - migrationBuilder.DropTable( - name: "Order_202009272214"); - - migrationBuilder.DropTable( - name: "Order_202009272215"); - - migrationBuilder.DropTable( - name: "Order_202009272216"); - - migrationBuilder.DropTable( - name: "Order_202009272217"); - } - } -} diff --git a/examples/Demo.DbMigrator/Migrations/CustomContextModelSnapshot.cs b/examples/Demo.DbMigrator/Migrations/CustomContextModelSnapshot.cs deleted file mode 100644 index 75732c5..0000000 --- a/examples/Demo.DbMigrator/Migrations/CustomContextModelSnapshot.cs +++ /dev/null @@ -1,66 +0,0 @@ -// -using System; -using Demo.DbMigrator; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Demo.DbMigrator.Migrations -{ - [DbContext(typeof(CustomContext))] - partial class CustomContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.8") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Demo.DbMigrator.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreateTime") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Order"); - }); - - modelBuilder.Entity("Demo.DbMigrator.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItem"); - }); - - modelBuilder.Entity("Demo.DbMigrator.OrderItem", b => - { - b.HasOne("Demo.DbMigrator.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/examples/Demo.HelloWorld/Program.cs b/examples/Demo.HelloWorld/Program.cs index 23b2dbf..c10d055 100644 --- a/examples/Demo.HelloWorld/Program.cs +++ b/examples/Demo.HelloWorld/Program.cs @@ -22,6 +22,7 @@ static async Task Main(string[] args) config.UseDatabase(Config.SQLITE1, DatabaseType.SQLite); }); var serviceProvider = services.BuildServiceProvider(); + new EFCoreShardingBootstrapper(serviceProvider).StartAsync(default).Wait(); using var scop = serviceProvider.CreateScope(); //拿到注入的IDbAccessor即可进行所有数据库操作 diff --git a/examples/Demo.ModSharding/Program.cs b/examples/Demo.ModSharding/Program.cs index f928c21..5dc3caa 100644 --- a/examples/Demo.ModSharding/Program.cs +++ b/examples/Demo.ModSharding/Program.cs @@ -26,6 +26,7 @@ static async Task Main(string[] args) config.SetHashModSharding(nameof(Base_UnitTest.Id), 3); }); var serviceProvider = services.BuildServiceProvider(); + new EFCoreShardingBootstrapper(serviceProvider).StartAsync(default).Wait(); using var scop = serviceProvider.CreateScope(); diff --git a/examples/Demo.Performance/Program.cs b/examples/Demo.Performance/Program.cs index 7180573..63ccd82 100644 --- a/examples/Demo.Performance/Program.cs +++ b/examples/Demo.Performance/Program.cs @@ -25,6 +25,7 @@ static void Main(string[] args) config.SetHashModSharding(nameof(Base_UnitTest.Id), 3); }); var serviceProvider = services.BuildServiceProvider(); + new EFCoreShardingBootstrapper(serviceProvider).StartAsync(default).Wait(); var db = serviceProvider.GetService(); var shardingDb = serviceProvider.GetService(); diff --git a/examples/Demo.ReadWrite/Program.cs b/examples/Demo.ReadWrite/Program.cs index 4630fe1..4663c94 100644 --- a/examples/Demo.ReadWrite/Program.cs +++ b/examples/Demo.ReadWrite/Program.cs @@ -28,6 +28,7 @@ static async Task Main(string[] args) }, DatabaseType.SQLite); }); var serviceProvider = services.BuildServiceProvider(); + new EFCoreShardingBootstrapper(serviceProvider).StartAsync(default).Wait(); using var scop = serviceProvider.CreateScope(); //拿到注入的IDbAccessor即可进行所有数据库操作 diff --git a/src/EFCore.Sharding.MySql/EFCore.Sharding.MySql.csproj b/src/EFCore.Sharding.MySql/EFCore.Sharding.MySql.csproj index b9bb669..7a657b5 100644 --- a/src/EFCore.Sharding.MySql/EFCore.Sharding.MySql.csproj +++ b/src/EFCore.Sharding.MySql/EFCore.Sharding.MySql.csproj @@ -9,7 +9,7 @@ true - + diff --git a/src/EFCore.Sharding.PostgreSql/EFCore.Sharding.PostgreSql.csproj b/src/EFCore.Sharding.PostgreSql/EFCore.Sharding.PostgreSql.csproj index 38e36ce..c81c6c5 100644 --- a/src/EFCore.Sharding.PostgreSql/EFCore.Sharding.PostgreSql.csproj +++ b/src/EFCore.Sharding.PostgreSql/EFCore.Sharding.PostgreSql.csproj @@ -13,6 +13,7 @@ + diff --git a/src/EFCore.Sharding.PostgreSql/PostgreSqlProvider.cs b/src/EFCore.Sharding.PostgreSql/PostgreSqlProvider.cs index 598f337..1fdae34 100644 --- a/src/EFCore.Sharding.PostgreSql/PostgreSqlProvider.cs +++ b/src/EFCore.Sharding.PostgreSql/PostgreSqlProvider.cs @@ -17,7 +17,7 @@ internal class PostgreSqlProvider : AbstractProvider public override void UseDatabase(DbContextOptionsBuilder dbContextOptionsBuilder, DbConnection dbConnection) { - dbContextOptionsBuilder.UseNpgsql(dbConnection); + dbContextOptionsBuilder.UseNpgsql(dbConnection, x => x.UseNetTopologySuite()); } } } diff --git a/src/EFCore.Sharding.SQLite/EFCore.Sharding.SQLite.csproj b/src/EFCore.Sharding.SQLite/EFCore.Sharding.SQLite.csproj index 915885b..be510fe 100644 --- a/src/EFCore.Sharding.SQLite/EFCore.Sharding.SQLite.csproj +++ b/src/EFCore.Sharding.SQLite/EFCore.Sharding.SQLite.csproj @@ -10,6 +10,7 @@ + diff --git a/src/EFCore.Sharding.SQLite/SQLiteProvider.cs b/src/EFCore.Sharding.SQLite/SQLiteProvider.cs index 1266764..d3846f8 100644 --- a/src/EFCore.Sharding.SQLite/SQLiteProvider.cs +++ b/src/EFCore.Sharding.SQLite/SQLiteProvider.cs @@ -15,7 +15,7 @@ internal class SQLiteProvider : AbstractProvider public override void UseDatabase(DbContextOptionsBuilder dbContextOptionsBuilder, DbConnection dbConnection) { - dbContextOptionsBuilder.UseSqlite(dbConnection); + dbContextOptionsBuilder.UseSqlite(dbConnection, x => x.UseNetTopologySuite()); } } } diff --git a/src/EFCore.Sharding.SqlServer.2x/EFCore.Sharding.SqlServer.2x.csproj b/src/EFCore.Sharding.SqlServer.2x/EFCore.Sharding.SqlServer.2x.csproj index adbde71..1c35d68 100644 --- a/src/EFCore.Sharding.SqlServer.2x/EFCore.Sharding.SqlServer.2x.csproj +++ b/src/EFCore.Sharding.SqlServer.2x/EFCore.Sharding.SqlServer.2x.csproj @@ -21,6 +21,7 @@ + diff --git a/src/EFCore.Sharding.SqlServer/EFCore.Sharding.SqlServer.csproj b/src/EFCore.Sharding.SqlServer/EFCore.Sharding.SqlServer.csproj index ccbd286..ebd24d9 100644 --- a/src/EFCore.Sharding.SqlServer/EFCore.Sharding.SqlServer.csproj +++ b/src/EFCore.Sharding.SqlServer/EFCore.Sharding.SqlServer.csproj @@ -13,6 +13,7 @@ + diff --git a/src/EFCore.Sharding.SqlServer/SqlServerProvider.cs b/src/EFCore.Sharding.SqlServer/SqlServerProvider.cs index 5377e92..7380ee8 100644 --- a/src/EFCore.Sharding.SqlServer/SqlServerProvider.cs +++ b/src/EFCore.Sharding.SqlServer/SqlServerProvider.cs @@ -20,11 +20,13 @@ internal class SqlServerProvider : AbstractProvider public override void UseDatabase(DbContextOptionsBuilder dbContextOptionsBuilder, DbConnection dbConnection) { -#if EFCORE3 - dbContextOptionsBuilder.UseSqlServer(dbConnection); -#elif EFCORE2 - dbContextOptionsBuilder.UseSqlServer(dbConnection, config => config.UseRowNumberForPaging()); + dbContextOptionsBuilder.UseSqlServer(dbConnection, x => + { + x.UseNetTopologySuite(); +#if EFCORE2 + x.UseRowNumberForPaging(); #endif + }); } } } diff --git a/src/EFCore.Sharding.Tests/EFCore.Sharding.Tests.csproj b/src/EFCore.Sharding.Tests/EFCore.Sharding.Tests.csproj index f210765..834c0d9 100644 --- a/src/EFCore.Sharding.Tests/EFCore.Sharding.Tests.csproj +++ b/src/EFCore.Sharding.Tests/EFCore.Sharding.Tests.csproj @@ -7,7 +7,6 @@ - diff --git a/src/EFCore.Sharding.Tests/Sharding/ShardingDbAccessorTest.cs b/src/EFCore.Sharding.Tests/Sharding/ShardingDbAccessorTest.cs index 09508e9..ec56ade 100644 --- a/src/EFCore.Sharding.Tests/Sharding/ShardingDbAccessorTest.cs +++ b/src/EFCore.Sharding.Tests/Sharding/ShardingDbAccessorTest.cs @@ -1,5 +1,4 @@ -using EFCore.Sharding.Tests.Util; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; diff --git a/src/EFCore.Sharding.Tests/Startup.cs b/src/EFCore.Sharding.Tests/Startup.cs index f9c37b8..d6107d0 100644 --- a/src/EFCore.Sharding.Tests/Startup.cs +++ b/src/EFCore.Sharding.Tests/Startup.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -15,26 +14,24 @@ public class Startup [AssemblyInitialize] public static void Begin(TestContext context) { - var host = Host.CreateDefaultBuilder() - .ConfigureServices(services => - { - services.AddEFCoreSharding(config => - { - config.UseDatabase(Config.CONSTRING1, DatabaseType.SqlServer); - config.UseDatabase(Config.SQLITE1, DatabaseType.SQLite); - config.UseDatabase(Config.SQLITE2, DatabaseType.SQLite); - config.UseDatabase(Config.CONSTRING1, DatabaseType.SqlServer); + ServiceCollection services = new ServiceCollection(); + services.AddEFCoreSharding(config => + { + config.UseDatabase(Config.CONSTRING1, DatabaseType.SqlServer); + config.UseDatabase(Config.SQLITE1, DatabaseType.SQLite); + config.UseDatabase(Config.SQLITE2, DatabaseType.SQLite); + config.UseDatabase(Config.CONSTRING1, DatabaseType.SqlServer); - //分表配置 - //添加数据源 - config.AddDataSource(Config.CONSTRING1, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); - //设置分表规则 - config.SetHashModSharding(nameof(Base_UnitTest.Id), 3); - }); - }).Build(); - host.Start(); + //分表配置 + //添加数据源 + config.AddDataSource(Config.CONSTRING1, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); + //设置分表规则 + config.SetHashModSharding(nameof(Base_UnitTest.Id), 3); + }); + + RootServiceProvider = services.BuildServiceProvider(); + new EFCoreShardingBootstrapper(RootServiceProvider).StartAsync(default).Wait(); - RootServiceProvider = host.Services; ServiceScopeFactory = RootServiceProvider.GetService(); } diff --git a/src/EFCore.Sharding/DbContext/GenericDbContext.cs b/src/EFCore.Sharding/DbContext/GenericDbContext.cs index 729ff39..0ef92b7 100644 --- a/src/EFCore.Sharding/DbContext/GenericDbContext.cs +++ b/src/EFCore.Sharding/DbContext/GenericDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using EFCore.Sharding.Util; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using System; using System.Collections.Generic; @@ -8,10 +9,24 @@ namespace EFCore.Sharding { + /// + /// 通用DbContext + /// public class GenericDbContext : DbContext { + /// + /// DbContext原生配置 + /// public DbContextOptions DbContextOption { get; } + + /// + /// 全局自定义配置 + /// public EFCoreShardingOptions ShardingOption { get; } + + /// + /// 构建参数 + /// public DbContextParamters Paramter { get; } /// @@ -41,6 +56,11 @@ public GenericDbContext(GenericDbContext dbContext) } private static readonly ValueConverter _dateTimeConverter = new ValueConverter(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Local)); + + /// + /// 模型构建 + /// + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { List entityTypes; @@ -64,6 +84,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entityTypes.ForEach(aEntity => { var entity = modelBuilder.Entity(aEntity); + ShardingOption.EntityTypeBuilderFilter?.Invoke(entity); if (!string.IsNullOrEmpty(Paramter.Suffix)) @@ -94,7 +115,26 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) property.SetValueConverter(_dateTimeConverter); } } + +#if EFCORE3 + //字段注释,需要开启程序集XML文档 + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + var comments = XmlHelper.GetProperyCommentBySummary(entityType.ClrType); + foreach (var property in entityType.GetProperties()) + { + if (comments.ContainsKey(property.Name)) + { + property.SetComment(comments[property.Name]); + } + } + } +#endif } + + /// + /// 取消跟踪 + /// public void Detach() { ChangeTracker.Entries().ToList().ForEach(aEntry => diff --git a/src/EFCore.Sharding/DbFactory.cs b/src/EFCore.Sharding/DbFactory.cs index 1f5ea1c..8667601 100644 --- a/src/EFCore.Sharding/DbFactory.cs +++ b/src/EFCore.Sharding/DbFactory.cs @@ -56,11 +56,6 @@ public void CreateTable(string conString, DatabaseType dbType, Type entityType, } public GenericDbContext GetDbContext(DbContextParamters options) { - if (options.ConnectionString.IsNullOrEmpty()) - { - throw new Exception("连接字符串不能为空"); - } - AbstractProvider provider = GetProvider(options.DbType); DbConnection dbConnection = provider.GetDbConnection(); diff --git a/src/EFCore.Sharding/DependencyInjection/EFCoreShardingBootstrapper.cs b/src/EFCore.Sharding/DependencyInjection/EFCoreShardingBootstrapper.cs index 89d4493..2111770 100644 --- a/src/EFCore.Sharding/DependencyInjection/EFCoreShardingBootstrapper.cs +++ b/src/EFCore.Sharding/DependencyInjection/EFCoreShardingBootstrapper.cs @@ -8,10 +8,19 @@ namespace EFCore.Sharding { + /// + /// EFCoreSharding初始化加载 + /// 注:非Host环境需要手动调用 + /// public class EFCoreShardingBootstrapper : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly EFCoreShardingOptions _shardingOptions; + + /// + /// 构造函数 + /// + /// public EFCoreShardingBootstrapper(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; @@ -19,6 +28,12 @@ public EFCoreShardingBootstrapper(IServiceProvider serviceProvider) Cache.ServiceProvider = serviceProvider; } + + /// + /// 加载 + /// + /// + /// protected override Task ExecuteAsync(CancellationToken stoppingToken) { _shardingOptions.Bootstrapper?.Invoke(_serviceProvider); diff --git a/src/EFCore.Sharding/DependencyInjection/IShardingBuilder.cs b/src/EFCore.Sharding/DependencyInjection/IShardingBuilder.cs index 8dfc012..460778b 100644 --- a/src/EFCore.Sharding/DependencyInjection/IShardingBuilder.cs +++ b/src/EFCore.Sharding/DependencyInjection/IShardingBuilder.cs @@ -35,6 +35,20 @@ public interface IShardingBuilder /// IShardingBuilder MigrationsWithoutForeignKey(); + /// + /// 是否在启动时自动创建分表,默认true + /// + /// 是否启用 + /// + IShardingBuilder CreateShardingTableOnStarting(bool enable); + + /// + /// 是否启用分表数据库迁移,默认false + /// + /// 是否启用 + /// + IShardingBuilder EnableShardingMigration(bool enable); + /// /// 使用逻辑删除 /// diff --git a/src/EFCore.Sharding/DependencyInjection/ShardingContainer.cs b/src/EFCore.Sharding/DependencyInjection/ShardingContainer.cs index 3a95acd..f08969b 100644 --- a/src/EFCore.Sharding/DependencyInjection/ShardingContainer.cs +++ b/src/EFCore.Sharding/DependencyInjection/ShardingContainer.cs @@ -111,6 +111,14 @@ private void CreateTable(IServiceProvider serviceProvider, string sourc #endif return allTables; } + private void AddShardingTable(string absTableName, string fullTableName) + { + if (!ExistsShardingTables.ContainsKey(absTableName)) + { + ExistsShardingTables.Add(absTableName, new List()); + } + ExistsShardingTables[absTableName].Add(fullTableName); + } #endregion @@ -183,6 +191,24 @@ public IShardingBuilder MigrationsWithoutForeignKey() return this; } + public IShardingBuilder CreateShardingTableOnStarting(bool enable) + { + _services.Configure(x => + { + x.CreateShardingTableOnStarting = enable; + }); + + return this; + } + public IShardingBuilder EnableShardingMigration(bool enable) + { + _services.Configure(x => + { + x.EnableShardingMigration = enable; + }); + + return this; + } public IShardingBuilder UseLogicDelete(string keyField = "Id", string deletedField = "Deleted") { _services.Configure(x => @@ -280,24 +306,24 @@ public IShardingBuilder SetDateSharding(string shardingField, ExpandByD { x.Bootstrapper += serviceProvider => { - (string conExpression, TimeSpan leadTime) paramter = + var sharingOption = serviceProvider.GetService>().Value; + + (string conExpression, string startTimeFormat, Func nextTime) paramter = expandByDateMode switch { - ExpandByDateMode.PerMinute => ("30 * * * * ? *", TimeSpan.FromSeconds(60)), - ExpandByDateMode.PerHour => ("0 30 * * * ? *", TimeSpan.FromMinutes(60)), - ExpandByDateMode.PerDay => ("0 0 23 * * ? *", TimeSpan.FromHours(2)), - ExpandByDateMode.PerMonth => ("0 0 0 L * ? *", TimeSpan.FromDays(2)), - ExpandByDateMode.PerYear => ("0 0 0 L 12 ? *", TimeSpan.FromDays(2)), + ExpandByDateMode.PerMinute => ("0 * * * * ? *", "yyyy/MM/dd HH:mm:00", x => x.AddMinutes(1)), + ExpandByDateMode.PerHour => ("0 0 * * * ? *", "yyyy/MM/dd HH:00:00", x => x.AddHours(1)), + ExpandByDateMode.PerDay => ("0 0 0 * * ? *", "yyyy/MM/dd 00:00:00", x => x.AddDays(1)), + ExpandByDateMode.PerMonth => ("0 0 0 1 * ? *", "yyyy/MM/01 00:00:00", x => x.AddMonths(1)), + ExpandByDateMode.PerYear => ("0 0 0 1 1 ? *", "yyyy/01/01 00:00:00", x => x.AddYears(1)), _ => throw new Exception("expandByDateMode参数无效") }; //确保之前的表已存在 var theTime = ranges.Min(x => x.startTime); + theTime = DateTime.Parse(theTime.ToString(paramter.startTimeFormat)); - var key = expandByDateMode.ToString().Replace("Per", ""); - var method = theTime.GetType().GetMethod($"Add{key}s"); - - DateTime endTime = (DateTime)method.Invoke(DateTime.Now, new object[] { 1 }) + paramter.leadTime; + DateTime endTime = paramter.nextTime(DateTime.Parse(DateTime.Now.ToString(paramter.startTimeFormat))); while (theTime <= endTime) { @@ -306,22 +332,23 @@ public IShardingBuilder SetDateSharding(string shardingField, ExpandByD string absTableName = AnnotationHelper.GetDbTableName(typeof(TEntity)); string fullTableName = $"{absTableName}_{suffix}"; - if (!ExistsShardingTables.ContainsKey(absTableName)) + AddShardingTable(absTableName, fullTableName); + + //启动时建表 + if (sharingOption.CreateShardingTableOnStarting) { - ExistsShardingTables.Add(absTableName, new List()); + CreateTable(serviceProvider, theSourceName, suffix); } - ExistsShardingTables[absTableName].Add(fullTableName); - CreateTable(serviceProvider, theSourceName, suffix); AddPhysicTable(suffix, theSourceName); - theTime = (DateTime)method.Invoke(theTime, new object[] { 1 }); + theTime = paramter.nextTime(theTime); } //定时自动建表 JobHelper.SetCronJob(() => { - DateTime trueDate = DateTime.Now + paramter.leadTime; + DateTime trueDate = paramter.nextTime(DateTime.Parse(DateTime.Now.ToString(paramter.startTimeFormat))); var theSourceName = GetSourceName(trueDate); string suffix = shardingRule.GetTableSuffixByField(trueDate); //添加物理表 @@ -331,7 +358,11 @@ public IShardingBuilder SetDateSharding(string shardingField, ExpandByD string GetSourceName(DateTime time) { - return ranges.Where(x => time >= x.startTime && time < x.endTime).FirstOrDefault().sourceName; + return ranges + .Where(x => time >= DateTime.Parse(x.startTime.ToString(paramter.startTimeFormat)) + && time < x.endTime) + .FirstOrDefault() + .sourceName; } }; }); @@ -359,11 +390,23 @@ public IShardingBuilder SetHashModSharding(string shardingField, int mo { x.Bootstrapper += serviceProvider => { + var sharingOption = serviceProvider.GetService>().Value; + //建表 for (int i = 0; i < mod; i++) { var sourceName = ranges.Where(x => i >= x.start && i < x.end).FirstOrDefault().sourceName; - CreateTable(serviceProvider, sourceName, i.ToString()); + + string absTableName = AnnotationHelper.GetDbTableName(typeof(TEntity)); + string fullTableName = $"{absTableName}_{i}"; + AddShardingTable(absTableName, fullTableName); + + //启动时建表 + if (sharingOption.CreateShardingTableOnStarting) + { + CreateTable(serviceProvider, sourceName, i.ToString()); + } + AddPhysicTable(i.ToString(), sourceName); } }; diff --git a/src/EFCore.Sharding/Migrations/ShardingMigration.cs b/src/EFCore.Sharding/Migrations/ShardingMigration.cs index 8f9ceb3..ad33f4e 100644 --- a/src/EFCore.Sharding/Migrations/ShardingMigration.cs +++ b/src/EFCore.Sharding/Migrations/ShardingMigration.cs @@ -54,8 +54,15 @@ public override IReadOnlyList GetDifferences(IModel source, } } - //分表 - resList.AddRange(sourceOperations.SelectMany(x => BuildShardingOperation(x))); + if (shardingOption.EnableShardingMigration) + { + //分表 + resList.AddRange(sourceOperations.SelectMany(x => BuildShardingOperation(x))); + } + else + { + resList.AddRange(sourceOperations); + } return resList; } @@ -113,14 +120,23 @@ string BuildPattern(string absTableName) private void ReplaceName(MigrationOperation theOperation, string sourceName, string targetName) { string name = theOperation.GetPropertyValue("Name") as string; - string tableName = theOperation.GetPropertyValue("TableName") as string; + string tableName = theOperation.GetPropertyValue("Table") as string; if (!tableName.IsNullOrEmpty()) { theOperation.SetPropertyValue("Table", targetName); } if (!name.IsNullOrEmpty() && !(theOperation is ColumnOperation)) { - theOperation.SetPropertyValue("Name", name.Replace(sourceName, targetName)); + string[] patterns = new string[] { $"^()({sourceName})()$", $"^()({sourceName})(_.*?)$", $"^(.*?_)({sourceName})(_.*?)$", $"^(.*?_)({sourceName})()$" }; + foreach (var aPattern in patterns) + { + if (Regex.IsMatch(name, aPattern)) + { + var newName = new Regex(aPattern).Replace(name, "${1}" + targetName + "$3"); + theOperation.SetPropertyValue("Name", newName); + break; + } + } } Func propertyWhere = x => x.PropertyType.IsGenericType diff --git a/src/EFCore.Sharding/Options/EFCoreShardingOptions.cs b/src/EFCore.Sharding/Options/EFCoreShardingOptions.cs index 7ec44b9..4b65c11 100644 --- a/src/EFCore.Sharding/Options/EFCoreShardingOptions.cs +++ b/src/EFCore.Sharding/Options/EFCoreShardingOptions.cs @@ -47,6 +47,16 @@ public class EFCoreShardingOptions /// public bool MigrationsWithoutForeignKey { get; set; } = false; + /// + /// 是否在启动时自动创建分表,默认true + /// + public bool CreateShardingTableOnStarting { get; set; } = true; + + /// + /// 是否启用分表数据库迁移,默认false + /// + public bool EnableShardingMigration { get; set; } = false; + private Type[] _types; private readonly object _typesLock = new object(); internal Type[] Types diff --git a/src/EFCore.Sharding/Util/XmlHelper.cs b/src/EFCore.Sharding/Util/XmlHelper.cs new file mode 100644 index 0000000..8cb09d8 --- /dev/null +++ b/src/EFCore.Sharding/Util/XmlHelper.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.XPath; + +namespace EFCore.Sharding.Util +{ + internal static class XmlHelper + { + /// + /// 通过XML获取类属性注释 + /// 参考:https://github.com/dotnetcore/FreeSql/blob/d266446062b1dfcd694f7d213191cd2383410025/FreeSql/Internal/CommonUtils.cs + /// + /// 类型 + /// Dict:key=属性名,value=注释 + public static Dictionary GetProperyCommentBySummary(Type type) + { + if (type.Assembly.IsDynamic) return null; + //动态生成的程序集,访问不了 Assembly.Location/Assembly.CodeBase + var regex = new Regex(@"\.(dll|exe)", RegexOptions.IgnoreCase); + var xmlPath = regex.Replace(type.Assembly.Location, ".xml"); + if (File.Exists(xmlPath) == false) + { + if (string.IsNullOrEmpty(type.Assembly.CodeBase)) return null; + xmlPath = regex.Replace(type.Assembly.CodeBase, ".xml"); + if (xmlPath.StartsWith("file:///") && Uri.TryCreate(xmlPath, UriKind.Absolute, out var tryuri)) + xmlPath = tryuri.LocalPath; + if (File.Exists(xmlPath) == false) return null; + } + + var dic = new Dictionary(); + var sReader = new StringReader(File.ReadAllText(xmlPath)); + using (var xmlReader = XmlReader.Create(sReader)) + { + XPathDocument xpath = null; + try + { + xpath = new XPathDocument(xmlReader); + } + catch + { + return null; + } + var xmlNav = xpath.CreateNavigator(); + + var className = (type.IsNested ? $"{type.Namespace}.{type.DeclaringType.Name}.{type.Name}" : $"{type.Namespace}.{type.Name}").Trim('.'); + var node = xmlNav.SelectSingleNode($"/doc/members/member[@name='T:{className}']/summary"); + if (node != null) + { + var comment = node.InnerXml.Trim(' ', '\r', '\n', '\t'); + if (string.IsNullOrEmpty(comment) == false) dic.Add("", comment); //class注释 + } + + var props = type.GetPropertiesDictIgnoreCase().Values; + foreach (var prop in props) + { + className = (prop.DeclaringType.IsNested ? $"{prop.DeclaringType.Namespace}.{prop.DeclaringType.DeclaringType.Name}.{prop.DeclaringType.Name}" : $"{prop.DeclaringType.Namespace}.{prop.DeclaringType.Name}").Trim('.'); + node = xmlNav.SelectSingleNode($"/doc/members/member[@name='P:{className}.{prop.Name}']/summary"); + if (node == null) continue; + var comment = node.InnerXml.Trim(' ', '\r', '\n', '\t'); + if (string.IsNullOrEmpty(comment)) continue; + + dic.Add(prop.Name, comment); + } + } + + return dic; + } + private static ConcurrentDictionary> _dicGetPropertiesDictIgnoreCase + = new ConcurrentDictionary>(); + private static Dictionary GetPropertiesDictIgnoreCase(this Type that) => that == null ? null : _dicGetPropertiesDictIgnoreCase.GetOrAdd(that, tp => + { + var props = that.GetProperties().GroupBy(p => p.DeclaringType).Reverse().SelectMany(p => p); //将基类的属性位置放在前面 #164 + var dict = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + foreach (var prop in props) + { + if (dict.ContainsKey(prop.Name)) continue; + dict.Add(prop.Name, prop); + } + return dict; + }); + } +}