当前位置:网站首页>How can dbcontext support the migration of different databases in efcore advanced SaaS system
How can dbcontext support the migration of different databases in efcore advanced SaaS system
2022-07-25 06:43:00 【Dotnet cross platform】
Preface
With the continuous development and iteration of the system, the default efcore Very powerful , But as the Saas Introduction of system efcore The multi tenancy pattern based on table fields is perfect , But multi tenancy based on database can also be used , But there are also disadvantages , The disadvantage is that there is no way to support different databases ,migration support multi database provider with single dbcontext, I am not talented , I inquired about , Official documents only state dbcontext How to realize the migration of multiple data sources , But lack is not a single dbcontext, This is a headache . So I wrote this blog with the principle of trying , Because I only have mmsql and mysql So this time we will use these two databases for testing
Advertising time
I have developed a efcore Separate table, database, read and write components
https://github.com/dotnetcore/sharding-core
Hope to have a little friend you like to give me a little star thank you
So don't talk more nonsense, let's start right away migration support multi database provider with single dbcontext
New projects
1. By the way, it depends on 
2. Create a new one User class
[Table(nameof(User))]
public class User{
public string UserId { get; set; }
public string UserName { get; set; }
}3. establish DbContext
public class MyDbContext:DbContext
{
public DbSet<User> Users { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options):base(options)
{
}4.StartUp To configure
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 MySql"builder.Services.AddDbContext<MyDbContext>(options =>
{
_ = provider switch
{ "MySql" => options.UseMySql("server=127.0.0.1;port=3306;database=DBMultiDataBase;userid=root;password=L6yBtV6qNENrwBy7;", new MySqlServerVersion(new Version())), "SqlServer" => options.UseSqlServer("Data Source=localhost;Initial Catalog=DBMultiDataBase;Integrated Security=True;"),
_ => throw new Exception($"Unsupported provider: {provider}")
};
});Migrate differentiated databases
Create a new migration namespace provider
public interface IMigrationNamespace
{ string GetNamespace();
}mysql and sqlserver The implementation of is project name migration folder
public class MySqlMigrationNamespace:IMigrationNamespace
{
public string GetNamespace()
{ return "EFCoreMigrateMultiDatabase.Migrations.MySql";
}
}
public class SqlServerMigrationNamespace:IMigrationNamespace
{
public string GetNamespace()
{ return "EFCoreMigrateMultiDatabase.Migrations.SqlServer";
}
}efcore Expand
add to efcore Expand
public class MigrationNamespaceExtension : IDbContextOptionsExtension
{
public IMigrationNamespace MigrationNamespace { get; }
public MigrationNamespaceExtension(IMigrationNamespace migrationNamespace)
{
MigrationNamespace = migrationNamespace;
}
public void ApplyServices(IServiceCollection services)
{
services.AddSingleton<IMigrationNamespace>(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(IDictionary<string, string> debugInfo)
{
}
public override bool IsDatabaseProvider => false;
public override string LogFragment => "MigrationNamespaceExtension";
}
}rewrite MigrationsAssembly Support multiple databases
public class EFCoreMultiDatabaseMigrationsAssembly: IMigrationsAssembly
{
public string MigrationNamespace { get; }
private readonly IMigrationsIdGenerator _idGenerator;
private readonly IDiagnosticsLogger<DbLoggerCategory.Migrations> _logger;
private IReadOnlyDictionary<string, 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.
/// </summary>
public EFCoreMultiDatabaseMigrationsAssembly(
IMigrationNamespace migrationNamespace,
ICurrentDbContext currentContext,
IDbContextOptions options,
IMigrationsIdGenerator idGenerator,
IDiagnosticsLogger<DbLoggerCategory.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.
/// </summary>
public virtual IReadOnlyDictionary<string, TypeInfo> Migrations
{
get
{
IReadOnlyDictionary<string, TypeInfo> Create()
{
var result = new SortedList<string, TypeInfo>();
var items
= from t in Assembly.GetConstructibleTypes()
where t.IsSubclassOf(typeof(Migration))&& print(t)
&& t.Namespace.Equals(MigrationNamespace)
&& t.GetCustomAttribute<DbContextAttribute>()?.ContextType == _contextType
let id = t.GetCustomAttribute<MigrationAttribute>()?.Id
orderby id
select (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.
/// </summary>
public 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.GetCustomAttribute<DbContextAttribute>()?.ContextType == _contextType
select (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.
/// </summary>
public 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.
/// </summary>
public 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.
/// </summary>
public virtual Migration CreateMigration(TypeInfo migrationClass, string activeProvider)
{
Console.WriteLine(migrationClass.FullName);
var migration = (Migration)Activator.CreateInstance(migrationClass.AsType())!;
migration.ActiveProvider = activeProvider; return migration;
}
} Fold To write startup
Reference resources https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/providers?tabs=vs
//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 SqlServer"builder.Services.AddDbContext<MyDbContext>(options =>
{
options.ReplaceService<IMigrationsAssembly, EFCoreMultiDatabaseMigrationsAssembly>();
_ = provider switch
{ "MySql" => options.UseMySql("server=127.0.0.1;port=3306;database=DBMultiDataBase;userid=root;password=L6yBtV6qNENrwBy7;", new MySqlServerVersion(new Version()))
.UseMigrationNamespace(new MySqlMigrationNamespace()), "SqlServer" => options.UseSqlServer("Data Source=localhost;Initial Catalog=DBMultiDataBase;Integrated Security=True;")
.UseMigrationNamespace(new SqlServerMigrationNamespace()),
_ => throw new Exception($"Unsupported provider: {provider}")
};
});So far, I think we should have achieved , But if we execute two migration commands separately, the previous one will be overwritten , After a whole afternoon debug The final finding of debugging is that when the migration script generates and writes files, it will judge the current DbContext' Of ModelSnapshot, The same dbcontext The generated files are the same , So we have two choices
1. Make the generated file names different
2. Give Way ModelSnapshot Do not perform deep query, and only process in the current directory
Here we choose the second
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;
}
}add to designservices
public class MyMigrationDesignTimeServices: IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IMigrationsScaffolder, MyMigrationsScaffolder>();
}
}transfer
Run two migration commands respectively 
Run the update database command 
Remember that we need to add options to the parameters
Next up
Next time we will realize efcore stay Saas Under the system multi-tenancy +code-first( transfer )+ table + sub-treasury + Read / write separation + Dynamic sub table + Dynamic sub Library + Dynamic read write separation + Dynamically add multi tenancy The whole process is zero sql Script Solutions for
Is it right? buffer Full stack
Last, last
Enclosed demo:EFCoreMigrateMultiDatabase https://github.com/xuejmnet/EFCoreMigrateMultiDatabase
You've seen it here. Are you sure you don't want to order star Or like it , a .Net Have to learn the sub database and sub table solution , It is simply understood as sharding-jdbc stay .net And support more features and better data aggregation , With native performance 97%, And no business intrusion , Support all non segmented efcore Native query
github Address https://github.com/xuejmnet/sharding-core
gitee Address https://gitee.com/dotnetchina/sharding-core
边栏推荐
- 如何学习 C 语言?
- [jailhouse article] base architectures for virtual physical computing (2018)
- Review of some classic exercises of arrays
- 四、MFC工具栏、运行时类信息机制、运行时创建机制
- MySQL queries the table name under the current database
- R strange grammar summary
- Case ---- how efficient is the buffer stream compared with the ordinary input stream and output stream?
- Detailed explanation of the difference, working principle and basic structure between NMOS and PMOS
- Addition, deletion, modification and query of DOM elements
- Quick sort code implementation
猜你喜欢

都说ScreenToGif是GIF录制神器,却不知其强大之处远不在此

Health clock in daily reminder tired? Then let automation help you -- hiflow, application connection automation assistant

MySQL remote login

labelme标注不同物体显示不同颜色以及批量转换

【transformer】DeiT

健康打卡每日提醒累了?那就让自动化帮你---HiFlow,应用连接自动化助手

Case ---- how efficient is the buffer stream compared with the ordinary input stream and output stream?

R strange grammar summary

RecycleView实现item重叠水平滑动
![[yolov5 practice 3] traffic sign recognition system based on yolov5 - model training](/img/2f/1d2938dafa17c602c9aaf640be9bf1.png)
[yolov5 practice 3] traffic sign recognition system based on yolov5 - model training
随机推荐
Baidu SEM bidding avoidance
Prevention strategy of Chang'an chain Shuanghua transaction
ArgoCD 用户管理、RBAC 控制、脚本登录、App 同步
Robot engineering - teaching quality - how to judge
C language -c51 compilation warning "* * * warning l1: unresolved external symbol" and extern
Temperature table lookup and calculation formula
机器学习两周学习成果
Ida Pro novice tutorial
DOM event type
Play with the one-stop plan of cann target detection and recognition [basic]
What does "TTL" mean in domain name resolution?
The code of Keil and Si compiler is not aligned??
Scientific computing library numpy Foundation & Improvement (Understanding + explanation of important functions)
Mlx90640 infrared thermal imager temperature measurement module development notes (I)
Create a new STM32 project and configure it - based on registers
Health clock in daily reminder tired? Then let automation help you -- hiflow, application connection automation assistant
JSON、
Common mode inductance has been heard many times, but what principle do you really understand?
Easy to understand: basic knowledge of MOS tube
C#读取倍福Beckhoff变量