diff --git a/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs b/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs index 4830541..4754e33 100644 --- a/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs +++ b/examples/Demo.DbMigrator/DesignTimeDbContextFactory.cs @@ -1,17 +1,25 @@ using EFCore.Sharding; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using System; namespace DbMigrator { - public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory, IDesignTimeServices + public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory { - public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) + static DesignTimeDbContextFactory() { - throw new System.NotImplementedException(); + ServiceCollection services = new ServiceCollection(); + services.AddEFCoreSharding(x => + { + x.MigrationsWithoutForeignKey(); + }); + ServiceProvider = services.BuildServiceProvider(); + new EFCoreShardingBootstrapper(ServiceProvider).StartAsync(default).Wait(); } + public static readonly IServiceProvider ServiceProvider; + /// /// 创建数据库上下文 /// @@ -19,17 +27,7 @@ public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) /// public CustomContext CreateDbContext(string[] args) { - using var host = Host.CreateDefaultBuilder() - .ConfigureServices(services => - { - services.AddEFCoreSharding(x => - { - x.MigrationsWithoutForeignKey(); - }); - }).Build(); - host.Start(); - - var db = host.Services + var db = ServiceProvider .GetService() .GetDbContext(new DbContextParamters { diff --git a/examples/Demo.DbMigrator/Migrations/20200926034223_InitialCreate.Designer.cs b/examples/Demo.DbMigrator/Migrations/20200926034223_InitialCreate.Designer.cs new file mode 100644 index 0000000..af9adfc --- /dev/null +++ b/examples/Demo.DbMigrator/Migrations/20200926034223_InitialCreate.Designer.cs @@ -0,0 +1,68 @@ +// +using System; +using 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("20200926034223_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("DateTime") + .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/20200926034223_InitialCreate.cs b/examples/Demo.DbMigrator/Migrations/20200926034223_InitialCreate.cs new file mode 100644 index 0000000..62480eb --- /dev/null +++ b/examples/Demo.DbMigrator/Migrations/20200926034223_InitialCreate.cs @@ -0,0 +1,50 @@ +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", + columns: table => new + { + Id = table.Column(nullable: false), + DateTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Order", 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"); + } + } +} diff --git a/examples/Demo.DbMigrator/Migrations/CustomContextModelSnapshot.cs b/examples/Demo.DbMigrator/Migrations/CustomContextModelSnapshot.cs new file mode 100644 index 0000000..96d35fc --- /dev/null +++ b/examples/Demo.DbMigrator/Migrations/CustomContextModelSnapshot.cs @@ -0,0 +1,66 @@ +// +using System; +using 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("DateTime") + .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/a.json b/examples/Demo.DbMigrator/a.json new file mode 100644 index 0000000..901c814 --- /dev/null +++ b/examples/Demo.DbMigrator/a.json @@ -0,0 +1 @@ +{"Name":"Order","Schema":null,"PrimaryKey":{"Schema":null,"Table":"Order","Name":"PK_Order","Columns":["Id"],"IsDestructiveChange":false},"Columns":[{"Name":"Id","Schema":null,"Table":"Order","ClrType":"System.Guid, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","ColumnType":null,"IsUnicode":null,"IsFixedLength":false,"MaxLength":null,"IsRowVersion":false,"IsNullable":false,"DefaultValue":null,"DefaultValueSql":null,"ComputedColumnSql":null,"Comment":null,"IsDestructiveChange":false},{"Name":"DateTime","Schema":null,"Table":"Order","ClrType":"System.DateTime, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","ColumnType":null,"IsUnicode":null,"IsFixedLength":false,"MaxLength":null,"IsRowVersion":false,"IsNullable":false,"DefaultValue":null,"DefaultValueSql":null,"ComputedColumnSql":null,"Comment":null,"IsDestructiveChange":false}],"ForeignKeys":[],"UniqueConstraints":[],"CheckConstraints":[],"Comment":null,"IsDestructiveChange":false}{"Name":"OrderItem","Schema":null,"PrimaryKey":{"Schema":null,"Table":"OrderItem","Name":"PK_OrderItem","Columns":["Id"],"IsDestructiveChange":false},"Columns":[{"Name":"Id","Schema":null,"Table":"OrderItem","ClrType":"System.Guid, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","ColumnType":null,"IsUnicode":null,"IsFixedLength":false,"MaxLength":null,"IsRowVersion":false,"IsNullable":false,"DefaultValue":null,"DefaultValueSql":null,"ComputedColumnSql":null,"Comment":null,"IsDestructiveChange":false},{"Name":"OrderId","Schema":null,"Table":"OrderItem","ClrType":"System.Guid, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","ColumnType":null,"IsUnicode":null,"IsFixedLength":false,"MaxLength":null,"IsRowVersion":false,"IsNullable":false,"DefaultValue":null,"DefaultValueSql":null,"ComputedColumnSql":null,"Comment":null,"IsDestructiveChange":false},{"Name":"Name","Schema":null,"Table":"OrderItem","ClrType":"System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","ColumnType":null,"IsUnicode":null,"IsFixedLength":false,"MaxLength":null,"IsRowVersion":false,"IsNullable":true,"DefaultValue":null,"DefaultValueSql":null,"ComputedColumnSql":null,"Comment":null,"IsDestructiveChange":false}],"ForeignKeys":[],"UniqueConstraints":[],"CheckConstraints":[],"Comment":null,"IsDestructiveChange":false} \ No newline at end of file diff --git a/src/EFCore.Sharding/DbFactory.cs b/src/EFCore.Sharding/DbFactory.cs index 60972f3..1f5ea1c 100644 --- a/src/EFCore.Sharding/DbFactory.cs +++ b/src/EFCore.Sharding/DbFactory.cs @@ -72,10 +72,7 @@ public GenericDbContext GetDbContext(DbContextParamters options) provider.UseDatabase(builder, dbConnection); builder.ReplaceService(); #if EFCORE3 - if (_shardingOptions.MigrationsWithoutForeignKey) - { - builder.ReplaceService(); - } + builder.ReplaceService(); #endif return new GenericDbContext(builder.Options, options, _shardingOptions); } diff --git a/src/EFCore.Sharding/Config/Cache.cs b/src/EFCore.Sharding/DependencyInjection/Cache.cs similarity index 100% rename from src/EFCore.Sharding/Config/Cache.cs rename to src/EFCore.Sharding/DependencyInjection/Cache.cs diff --git a/src/EFCore.Sharding/Config/Bootstrapper.cs b/src/EFCore.Sharding/DependencyInjection/EFCoreShardingBootstrapper.cs similarity index 69% rename from src/EFCore.Sharding/Config/Bootstrapper.cs rename to src/EFCore.Sharding/DependencyInjection/EFCoreShardingBootstrapper.cs index 79f552d..89d4493 100644 --- a/src/EFCore.Sharding/Config/Bootstrapper.cs +++ b/src/EFCore.Sharding/DependencyInjection/EFCoreShardingBootstrapper.cs @@ -1,4 +1,5 @@ using EFCore.Sharding.Config; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using System; @@ -7,14 +8,15 @@ namespace EFCore.Sharding { - internal class Bootstrapper : BackgroundService + public class EFCoreShardingBootstrapper : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly EFCoreShardingOptions _shardingOptions; - public Bootstrapper(IServiceProvider serviceProvider, IOptions shardingOptions) + public EFCoreShardingBootstrapper(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - _shardingOptions = shardingOptions.Value; + _shardingOptions = serviceProvider.GetService>().Value; + Cache.ServiceProvider = serviceProvider; } protected override Task ExecuteAsync(CancellationToken stoppingToken) diff --git a/src/EFCore.Sharding/Config/EFCoreShardingExtensions.cs b/src/EFCore.Sharding/DependencyInjection/EFCoreShardingExtensions.cs similarity index 94% rename from src/EFCore.Sharding/Config/EFCoreShardingExtensions.cs rename to src/EFCore.Sharding/DependencyInjection/EFCoreShardingExtensions.cs index a41d85e..25d7586 100644 --- a/src/EFCore.Sharding/Config/EFCoreShardingExtensions.cs +++ b/src/EFCore.Sharding/DependencyInjection/EFCoreShardingExtensions.cs @@ -28,7 +28,7 @@ public static IServiceCollection AddEFCoreSharding(this IServiceCollection servi services.AddSingleton(); services.AddScoped(); - services.AddHostedService(); + services.AddHostedService(); return services; } diff --git a/src/EFCore.Sharding/Config/IShardingBuilder.cs b/src/EFCore.Sharding/DependencyInjection/IShardingBuilder.cs similarity index 100% rename from src/EFCore.Sharding/Config/IShardingBuilder.cs rename to src/EFCore.Sharding/DependencyInjection/IShardingBuilder.cs diff --git a/src/EFCore.Sharding/Config/IShardingConfig.cs b/src/EFCore.Sharding/DependencyInjection/IShardingConfig.cs similarity index 100% rename from src/EFCore.Sharding/Config/IShardingConfig.cs rename to src/EFCore.Sharding/DependencyInjection/IShardingConfig.cs diff --git a/src/EFCore.Sharding/Config/ShardingConstant.cs b/src/EFCore.Sharding/DependencyInjection/ShardingConstant.cs similarity index 100% rename from src/EFCore.Sharding/Config/ShardingConstant.cs rename to src/EFCore.Sharding/DependencyInjection/ShardingConstant.cs diff --git a/src/EFCore.Sharding/Config/ShardingContainer.cs b/src/EFCore.Sharding/DependencyInjection/ShardingContainer.cs similarity index 100% rename from src/EFCore.Sharding/Config/ShardingContainer.cs rename to src/EFCore.Sharding/DependencyInjection/ShardingContainer.cs diff --git a/src/EFCore.Sharding/Migrations/MigrationsWithoutForeignKey.cs b/src/EFCore.Sharding/Migrations/ShardingMigration.cs similarity index 56% rename from src/EFCore.Sharding/Migrations/MigrationsWithoutForeignKey.cs rename to src/EFCore.Sharding/Migrations/ShardingMigration.cs index ea578e0..484d1b6 100644 --- a/src/EFCore.Sharding/Migrations/MigrationsWithoutForeignKey.cs +++ b/src/EFCore.Sharding/Migrations/ShardingMigration.cs @@ -1,4 +1,5 @@ #if EFCORE3 +using EFCore.Sharding.Config; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; @@ -7,16 +8,20 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Update.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; namespace EFCore.Sharding { [SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "<挂起>")] - internal class MigrationsWithoutForeignKey : MigrationsModelDiffer + internal class ShardingMigration : MigrationsModelDiffer { - public MigrationsWithoutForeignKey( + public ShardingMigration( IRelationalTypeMappingSource typeMappingSource, IMigrationsAnnotationProvider migrationsAnnotations, IChangeDetector changeDetector, @@ -25,22 +30,30 @@ CommandBatchPreparerDependencies commandBatchPreparerDependencies ) : base(typeMappingSource, migrationsAnnotations, changeDetector, updateAdapterFactory, commandBatchPreparerDependencies) { - //_dbFactory = dbFactory; } public override IReadOnlyList GetDifferences(IModel source, IModel target) { - var operations = base.GetDifferences(source, target) - .Where(op => !(op is AddForeignKeyOperation)) - .Where(op => !(op is DropForeignKeyOperation)) - .ToList(); + List resList = new List(); - foreach (var operation in operations.OfType()) + var shardingOption = Cache.ServiceProvider.GetService>().Value; + var sourceOperations = base.GetDifferences(source, target).ToList(); + + //忽略外键 + if (shardingOption.MigrationsWithoutForeignKey) { - operation.ForeignKeys?.Clear(); + sourceOperations.RemoveAll(x => x is AddForeignKeyOperation || x is DropForeignKeyOperation); + foreach (var operation in sourceOperations.OfType()) + { + operation.ForeignKeys?.Clear(); + } } + resList.AddRange(sourceOperations); + + //分表 + - return operations; + return resList; } } } diff --git a/src/EFCore.Sharding/Util/DeepCloneExtensions.cs b/src/EFCore.Sharding/Util/DeepCloneExtensions.cs new file mode 100644 index 0000000..4e6fc73 --- /dev/null +++ b/src/EFCore.Sharding/Util/DeepCloneExtensions.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace EFCore.Sharding +{ + internal static class DeepCloneExtensions + { + public static T DeepClone(this T original) + { + return (T)DeepClone((object)original); + } + private static readonly MethodInfo CloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); + private static bool IsPrimitive(this Type type) + { + if (type == typeof(string)) return true; + return (type.IsValueType & type.IsPrimitive); + } + private static object DeepClone(this object originalObject) + { + return InternalCopy(originalObject, new Dictionary(new ReferenceEqualityComparer())); + } + private static object InternalCopy(object originalObject, IDictionary visited) + { + if (originalObject == null) return null; + var typeToReflect = originalObject.GetType(); + if (IsPrimitive(typeToReflect)) return originalObject; + if (visited.ContainsKey(originalObject)) return visited[originalObject]; + if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null; + var cloneObject = CloneMethod.Invoke(originalObject, null); + if (typeToReflect.IsArray) + { + var arrayType = typeToReflect.GetElementType(); + if (IsPrimitive(arrayType) == false) + { + Array clonedArray = (Array)cloneObject; + ArrayExtensions.ForEach(clonedArray, (array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices)); + } + } + visited.Add(originalObject, cloneObject); + CopyFields(originalObject, visited, cloneObject, typeToReflect); + RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect); + return cloneObject; + } + private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect) + { + if (typeToReflect.BaseType != null) + { + RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType); + CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate); + } + } + private static void CopyFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func filter = null) + { + foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags)) + { + if (filter != null && filter(fieldInfo) == false) continue; + if (IsPrimitive(fieldInfo.FieldType)) continue; + var originalFieldValue = fieldInfo.GetValue(originalObject); + var clonedFieldValue = InternalCopy(originalFieldValue, visited); + fieldInfo.SetValue(cloneObject, clonedFieldValue); + } + } + private class ReferenceEqualityComparer : EqualityComparer + { + public override bool Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + public override int GetHashCode(object obj) + { + if (obj == null) return 0; + return obj.GetHashCode(); + } + } + private static class ArrayExtensions + { + public static void ForEach(Array array, Action action) + { + if (array.LongLength == 0) return; + ArrayTraverse walker = new ArrayTraverse(array); + do action(array, walker.Position); + while (walker.Step()); + } + } + + internal class ArrayTraverse + { + public int[] Position; + private int[] maxLengths; + + public ArrayTraverse(Array array) + { + maxLengths = new int[array.Rank]; + for (int i = 0; i < array.Rank; ++i) + { + maxLengths[i] = array.GetLength(i) - 1; + } + Position = new int[array.Rank]; + } + + public bool Step() + { + for (int i = 0; i < Position.Length; ++i) + { + if (Position[i] < maxLengths[i]) + { + Position[i]++; + for (int j = 0; j < i; j++) + { + Position[j] = 0; + } + return true; + } + } + return false; + } + } + } +} \ No newline at end of file