社区网站建设方案书,中山精品网站建设公司,泰安高端网站设计建设,怎样查看网站建设时间前言随着系统的不断开发和迭代默认的efcore功能十分强大#xff0c;但是随着Saas系统的引进efcore基于表字段的多租户模式已经非常完美了#xff0c;但是基于数据库的多租户也是可以用的#xff0c;但是也存在缺点#xff0c;缺点就是没有办法支持不同数据库#xff0c;mi… 前言随着系统的不断开发和迭代默认的efcore功能十分强大但是随着Saas系统的引进efcore基于表字段的多租户模式已经非常完美了但是基于数据库的多租户也是可以用的但是也存在缺点缺点就是没有办法支持不同数据库migration support multi database provider with single dbcontext本人不才查询了一下官方文档只说明了dbcontext的迁移如何实现多数据源但是缺不是单个dbcontext,这个就让人很头疼。所以秉着尝试一下的原则进行了这篇博客的编写,因为本人只有mmsql和mysql所以这次就用这两个数据库来做测试广告时间本人开发了一款efcore的分表分库读写分离组件https://github.com/dotnetcore/sharding-core希望有喜欢的小伙伴给我点点star谢谢那么废话不多说我们马上开始migration support multi database provider with single dbcontext新建项目1.按装依赖2.新建一个User类[Table(nameof(User))]
public class User{public string UserId { get; set; }public string UserName { get; set; }
}3.创建DbContextpublic class MyDbContext:DbContext
{public DbSetUser Users { get; set; }public MyDbContext(DbContextOptionsMyDbContext options):base(options){}4.StartUp配置var provider builder.Configuration.GetValue(Provider, UnKnown);//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\SqlServer -Args --provider SqlServer//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\MySql -Args --provider MySqlbuilder.Services.AddDbContextMyDbContext(options
{_ provider switch{ MySql options.UseMySql(server127.0.0.1;port3306;databaseDBMultiDataBase;useridroot;passwordL6yBtV6qNENrwBy7;, new MySqlServerVersion(new Version())), SqlServer options.UseSqlServer(Data Sourcelocalhost;Initial CatalogDBMultiDataBase;Integrated SecurityTrue;),_ throw new Exception($Unsupported provider: {provider})};
});迁移区分数据库新建一个迁移命名空间提供者public interface IMigrationNamespace{ string GetNamespace();}mysql和sqlserver的实现分别是项目名称迁移文件夹public class MySqlMigrationNamespace:IMigrationNamespace{public string GetNamespace(){ return EFCoreMigrateMultiDatabase.Migrations.MySql;}}public class SqlServerMigrationNamespace:IMigrationNamespace{public string GetNamespace(){ return EFCoreMigrateMultiDatabase.Migrations.SqlServer;}}efcore扩展添加efcore扩展public class MigrationNamespaceExtension : IDbContextOptionsExtension{public IMigrationNamespace MigrationNamespace { get; }public MigrationNamespaceExtension(IMigrationNamespace migrationNamespace){MigrationNamespace migrationNamespace;}public void ApplyServices(IServiceCollection services){services.AddSingletonIMigrationNamespace(sp MigrationNamespace);}public void Validate(IDbContextOptions options){}public DbContextOptionsExtensionInfo Info new MigrationNamespaceExtensionInfo(this);private class MigrationNamespaceExtensionInfo : DbContextOptionsExtensionInfo{private readonly MigrationNamespaceExtension _migrationNamespaceExtension;public MigrationNamespaceExtensionInfo(IDbContextOptionsExtension extension) : base(extension){_migrationNamespaceExtension (MigrationNamespaceExtension)extension;}public override int GetServiceProviderHashCode() _migrationNamespaceExtension.MigrationNamespace.GetNamespace().GetHashCode();public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) true;public override void PopulateDebugInfo(IDictionarystring, string debugInfo){}public override bool IsDatabaseProvider false;public override string LogFragment MigrationNamespaceExtension;}}重写MigrationsAssembly支持多数据库public class EFCoreMultiDatabaseMigrationsAssembly: IMigrationsAssembly{public string MigrationNamespace { get; }private readonly IMigrationsIdGenerator _idGenerator;private readonly IDiagnosticsLoggerDbLoggerCategory.Migrations _logger;private IReadOnlyDictionarystring, TypeInfo? _migrations;private ModelSnapshot? _modelSnapshot;private readonly Type _contextType; /// summary/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to/// the same compatibility standards as public APIs. It may be changed or removed without notice in/// any release. You should only use it directly in your code with extreme caution and knowing that/// doing so can result in application failures when updating to a new Entity Framework Core release./// /summarypublic EFCoreMultiDatabaseMigrationsAssembly(IMigrationNamespace migrationNamespace,ICurrentDbContext currentContext,IDbContextOptions options,IMigrationsIdGenerator idGenerator,IDiagnosticsLoggerDbLoggerCategory.Migrations logger){_contextType currentContext.Context.GetType();var assemblyName RelationalOptionsExtension.Extract(options)?.MigrationsAssembly;Assembly assemblyName null? _contextType.Assembly: Assembly.Load(new AssemblyName(assemblyName));MigrationNamespace migrationNamespace.GetNamespace();_idGenerator idGenerator;_logger logger;} /// summary/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to/// the same compatibility standards as public APIs. It may be changed or removed without notice in/// any release. You should only use it directly in your code with extreme caution and knowing that/// doing so can result in application failures when updating to a new Entity Framework Core release./// /summarypublic virtual IReadOnlyDictionarystring, TypeInfo Migrations{get{IReadOnlyDictionarystring, TypeInfo Create(){var result new SortedListstring, TypeInfo();var items from t in Assembly.GetConstructibleTypes()where t.IsSubclassOf(typeof(Migration)) print(t) t.Namespace.Equals(MigrationNamespace) t.GetCustomAttributeDbContextAttribute()?.ContextType _contextTypelet id t.GetCustomAttributeMigrationAttribute()?.Idorderby idselect (id, t);Console.WriteLine(Migrations: items.Count());foreach (var (id, t) in items){ if (id null){_logger.MigrationAttributeMissingWarning(t); continue;}result.Add(id, t);} return result;} return _migrations ?? Create();}}private bool print(TypeInfo t){Console.WriteLine(MigrationNamespace);Console.WriteLine(t.Namespace); return true;} /// summary/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to/// the same compatibility standards as public APIs. It may be changed or removed without notice in/// any release. You should only use it directly in your code with extreme caution and knowing that/// doing so can result in application failures when updating to a new Entity Framework Core release./// /summarypublic virtual ModelSnapshot? ModelSnapshot GetMod();private ModelSnapshot GetMod(){Console.WriteLine(_modelSnapshot: _modelSnapshot); if (_modelSnapshot null){Console.WriteLine(_modelSnapshot:null);_modelSnapshot (from t in Assembly.GetConstructibleTypes()where t.IsSubclassOf(typeof(ModelSnapshot)) print(t) MigrationNamespace.Equals(t?.Namespace) t.GetCustomAttributeDbContextAttribute()?.ContextType _contextTypeselect (ModelSnapshot)Activator.CreateInstance(t.AsType())!).FirstOrDefault();Console.WriteLine(_modelSnapshot: _modelSnapshot);} return _modelSnapshot;} /// summary/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to/// the same compatibility standards as public APIs. It may be changed or removed without notice in/// any release. You should only use it directly in your code with extreme caution and knowing that/// doing so can result in application failures when updating to a new Entity Framework Core release./// /summarypublic virtual Assembly Assembly { get; } /// summary/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to/// the same compatibility standards as public APIs. It may be changed or removed without notice in/// any release. You should only use it directly in your code with extreme caution and knowing that/// doing so can result in application failures when updating to a new Entity Framework Core release./// /summarypublic virtual string? FindMigrationId(string nameOrId) Migrations.Keys.Where(_idGenerator.IsValidId(nameOrId) // ReSharper disable once ImplicitlyCapturedClosure? id string.Equals(id, nameOrId, StringComparison.OrdinalIgnoreCase): id string.Equals(_idGenerator.GetName(id), nameOrId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); /// summary/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to/// the same compatibility standards as public APIs. It may be changed or removed without notice in/// any release. You should only use it directly in your code with extreme caution and knowing that/// doing so can result in application failures when updating to a new Entity Framework Core release./// /summarypublic virtual Migration CreateMigration(TypeInfo migrationClass, string activeProvider){Console.WriteLine(migrationClass.FullName);var migration (Migration)Activator.CreateInstance(migrationClass.AsType())!;migration.ActiveProvider activeProvider; return migration;}}折叠编写startup参考 https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/providers?tabsvs//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\SqlServer -Args --provider SqlServer//Add-Migration InitialCreate -Context MyDbContext -OutputDir Migrations\MySql -Args --provider MySql//update-database -Args --provider MySql//update-database -Args --provider SqlServerbuilder.Services.AddDbContextMyDbContext(options
{options.ReplaceServiceIMigrationsAssembly, EFCoreMultiDatabaseMigrationsAssembly();_ provider switch{ MySql options.UseMySql(server127.0.0.1;port3306;databaseDBMultiDataBase;useridroot;passwordL6yBtV6qNENrwBy7;, new MySqlServerVersion(new Version())).UseMigrationNamespace(new MySqlMigrationNamespace()), SqlServer options.UseSqlServer(Data Sourcelocalhost;Initial CatalogDBMultiDataBase;Integrated SecurityTrue;).UseMigrationNamespace(new SqlServerMigrationNamespace()),_ throw new Exception($Unsupported provider: {provider})};
});到此为止我这边想我们应该已经实现了把,但是如果我们分别执行两个迁移命令会导致前一个迁移命令被覆盖掉,经过一整个下午的debug调试最后发现是因为在迁移脚本生成写入文件的时候会判断当前DbContext的ModelSnapshot,同一个dbcontext生成的文件是一样的所以我们这边有两个选择1.让生成的文件名不一样2.让ModelSnapshot不进行深度查询只在当前目录下处理这边选了第二种public class MyMigrationsScaffolder: MigrationsScaffolder{private readonly Type _contextType;public MyMigrationsScaffolder(MigrationsScaffolderDependencies dependencies) : base(dependencies){_contextType dependencies.CurrentContext.Context.GetType();}protected override string GetDirectory(string projectDir, string? siblingFileName, string subnamespace){var defaultDirectory Path.Combine(projectDir, Path.Combine(subnamespace.Split(.))); if (siblingFileName ! null){ if (!siblingFileName.StartsWith(_contextType.Name ModelSnapshot.)){var siblingPath TryGetProjectFile(projectDir, siblingFileName); if (siblingPath ! null){var lastDirectory Path.GetDirectoryName(siblingPath)!; if (!defaultDirectory.Equals(lastDirectory, StringComparison.OrdinalIgnoreCase)){Dependencies.OperationReporter.WriteVerbose(DesignStrings.ReusingNamespace(siblingFileName)); return lastDirectory;}}}} return defaultDirectory;}}添加designservicespublic class MyMigrationDesignTimeServices: IDesignTimeServices{public void ConfigureDesignTimeServices(IServiceCollection serviceCollection){serviceCollection.AddSingletonIMigrationsScaffolder, MyMigrationsScaffolder();}}迁移分别运行两个迁移命令运行更新数据库命令记得我们需要在参数里面添加选项下期预告下期我们将实现efcore在Saas系统下的多租户code-first(迁移)分表分库读写分离动态分表动态分库动态读写分离动态添加多租户 全程零sql脚本的解决方案是不是buffer叠满最后的最后附上demo:EFCoreMigrateMultiDatabase https://github.com/xuejmnet/EFCoreMigrateMultiDatabase您都看到这边了确定不点个star或者赞吗,一款.Net不得不学的分库分表解决方案,简单理解为sharding-jdbc在.net中的实现并且支持更多特性和更优秀的数据聚合,拥有原生性能的97%,并且无业务侵入性,支持未分片的所有efcore原生查询github地址 https://github.com/xuejmnet/sharding-coregitee地址 https://gitee.com/dotnetchina/sharding-core