添加oracle的code-first下的bug [#188]

This commit is contained in:
xuejiaming 2022-09-21 15:06:32 +08:00
parent 7326656ff4
commit 19aee26e85
8 changed files with 333 additions and 5 deletions

View File

@ -71,6 +71,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src6x", "src6x", "{BB80F31B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShardingCore.6x", "src6x\ShardingCore.6x\ShardingCore.6x.csproj", "{1FF83CC3-A5F2-45E0-AA39-23C4DDF87D71}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.OracleIssue", "samples\Sample.OracleIssue\Sample.OracleIssue.csproj", "{BF4FEA2A-3F09-47D8-9BF7-4261D8D1671D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -181,6 +183,10 @@ Global
{1FF83CC3-A5F2-45E0-AA39-23C4DDF87D71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FF83CC3-A5F2-45E0-AA39-23C4DDF87D71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FF83CC3-A5F2-45E0-AA39-23C4DDF87D71}.Release|Any CPU.Build.0 = Release|Any CPU
{BF4FEA2A-3F09-47D8-9BF7-4261D8D1671D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF4FEA2A-3F09-47D8-9BF7-4261D8D1671D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF4FEA2A-3F09-47D8-9BF7-4261D8D1671D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF4FEA2A-3F09-47D8-9BF7-4261D8D1671D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -212,6 +218,7 @@ Global
{3E895438-E609-4860-8391-6897ED55DA06} = {CC2C88C0-65F2-445D-BE78-973B840FE281}
{BAB101D4-2BFF-44CE-90E3-8B72DDB266B8} = {EDF8869A-C1E1-491B-BC9F-4A33F4DE1C73}
{1FF83CC3-A5F2-45E0-AA39-23C4DDF87D71} = {BB80F31B-37F1-44C2-BCFA-F45D1AC765FE}
{BF4FEA2A-3F09-47D8-9BF7-4261D8D1671D} = {EDF8869A-C1E1-491B-BC9F-4A33F4DE1C73}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8C07A667-E8B4-43C7-8053-721584BAD291}

View File

@ -3,6 +3,9 @@ using Microsoft.EntityFrameworkCore;
using Sample.MySql.DbContexts;
using Sample.MySql.Domain.Entities;
using Sample.MySql.multi;
using Sample.MySql.Shardings;
using ShardingCore.Core.RuntimeContexts;
using ShardingCore.Core.VirtualRoutes.TableRoutes.Abstractions;
namespace Sample.MySql.Controllers
{
@ -17,15 +20,24 @@ namespace Sample.MySql.Controllers
{
private readonly DefaultShardingDbContext _defaultTableDbContext;
private readonly IShardingRuntimeContext _shardingRuntimeContext;
public WeatherForecastController(DefaultShardingDbContext defaultTableDbContext)
public WeatherForecastController(DefaultShardingDbContext defaultTableDbContext,IShardingRuntimeContext shardingRuntimeContext)
{
_defaultTableDbContext = defaultTableDbContext;
_shardingRuntimeContext = shardingRuntimeContext;
}
public IQueryable<SysTest> GetAll()
{
var shardingTableCreator = _shardingRuntimeContext.GetShardingTableCreator();
var tableRouteManager = _shardingRuntimeContext.GetTableRouteManager();
//系统的时间分片都会实现 ITailAppendable 如果不是系统的自定义的转成你自己的对象即可
var virtualTableRoute = (ITailAppendable)tableRouteManager.GetRoute(typeof(SysUserMod));
//一定要先在路由里面添加尾巴
virtualTableRoute.Append("20220921");
shardingTableCreator.CreateTable<SysUserMod>("ds0","20220921");
return _defaultTableDbContext.Set<SysTest>();
}
[HttpGet]

View File

@ -0,0 +1,249 @@
using System.Collections;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Storage;
using Oracle.EntityFrameworkCore.Migrations;
using ShardingCore.Core.RuntimeContexts;
using ShardingCore.Core.ShardingMigrations.Abstractions;
using ShardingCore.Core.VirtualDatabase.VirtualDataSources;
using ShardingCore.Extensions;
using ShardingCore.Helpers;
namespace Sample.OracleIssue;
public class OracleMigrationHelper
{
private OracleMigrationHelper()
{
}
public static void Generate(
IShardingRuntimeContext shardingRuntimeContext,
MigrationOperation operation,
MigrationCommandListBuilder builder,
ISqlGenerationHelper sqlGenerationHelper,
List<MigrationCommand> addCmds
)
{
var migrationCommands = (List<OracleMigrationCommand>)builder.GetFieldValue("_commands");
var shardingMigrationManager = shardingRuntimeContext.GetRequiredService<IShardingMigrationManager>();
var virtualDataSource = shardingRuntimeContext.GetRequiredService<IVirtualDataSource>();
var currentCurrentDataSourceName = shardingMigrationManager.Current?.CurrentDataSourceName ??
virtualDataSource.DefaultDataSourceName;
addCmds.ForEach(aAddCmd =>
{
var (migrationResult, shardingCmds) = BuildDataSourceShardingCmds(shardingRuntimeContext,
virtualDataSource.DefaultDataSourceName, currentCurrentDataSourceName, operation,
aAddCmd.CommandText, sqlGenerationHelper);
if (!migrationResult.InDataSource)
{
if (migrationResult.CommandType == MigrationCommandTypeEnum.TableCommand)
{
migrationCommands.Remove((OracleMigrationCommand)aAddCmd);
}
}
else
{
if (migrationResult.CommandType == MigrationCommandTypeEnum.TableCommand)
{
//如果是分表
if (shardingCmds.IsNotEmpty())
{
migrationCommands.Remove((OracleMigrationCommand)aAddCmd);
//针对builder的原始表进行移除
shardingCmds.ForEach(aShardingCmd =>
{
builder.Append(aShardingCmd)
.EndCommand();
});
}
}
}
});
}
private static (MigrationResult migrationResult, List<string>) BuildDataSourceShardingCmds(
IShardingRuntimeContext shardingRuntimeContext, string defaultDataSourceName, string dataSourceName,
MigrationOperation operation, string sourceCmd, ISqlGenerationHelper sqlGenerationHelper)
{
//所有MigrationOperation定义
//https://github.com/dotnet/efcore/tree/b970bf29a46521f40862a01db9e276e6448d3cb0/src/EFCore.Relational/Migrations/Operations
//ColumnOperation仅替换Table
//其余其余都是将Name和Table使用分表名替换
var dataSourceRouteManager = shardingRuntimeContext.GetDataSourceRouteManager();
var entityMetadataManager = shardingRuntimeContext.GetEntityMetadataManager();
var tableRouteManager = shardingRuntimeContext.GetTableRouteManager();
var tableRoutes = tableRouteManager.GetRoutes();
var existsShardingTables = tableRoutes.ToDictionary(o => o.EntityMetadata.LogicTableName,
o => o.GetTails().Select(p => $"{o.EntityMetadata.LogicTableName}{o.EntityMetadata.TableSeparator}{p}")
.ToList());
//Dictionary<string, List<string>> _existsShardingTables
// = Cache.ServiceProvider.GetService<ShardingContainer>().ExistsShardingTables;
List<string> resList = new List<string>();
string absTableName = string.Empty;
string name = operation.GetPropertyValue("Name") as string;
string tableName = operation.GetPropertyValue("Table") as string;
string pattern = string.Format("^({0})$|^({0}_.*?)$|^(.*?_{0}_.*?)$|^(.*?_{0})$", absTableName);
Func<KeyValuePair<string, List<string>>, bool> where = x =>
existsShardingTables.Any(y => x.Key == y.Key && Regex.IsMatch(name, BuildPattern(y.Key)));
if (!string.IsNullOrWhiteSpace(tableName))
{
absTableName = tableName;
}
else if (!string.IsNullOrWhiteSpace(name))
{
if (existsShardingTables.Any(x => where(x)))
{
absTableName = existsShardingTables.FirstOrDefault(x => where(x)).Key;
}
else
{
absTableName = name;
}
}
MigrationResult migrationResult = new MigrationResult();
var entityMetadatas = entityMetadataManager.TryGetByLogicTableName(absTableName);
if (entityMetadatas.IsNotEmpty())
{
migrationResult.CommandType = MigrationCommandTypeEnum.TableCommand;
bool isShardingDataSource = entityMetadatas.Count == 1 && entityMetadatas[0].IsShardingDataSource();
if (isShardingDataSource)
{
var virtualDataSourceRoute = dataSourceRouteManager.GetRoute(entityMetadatas[0].EntityType);
isShardingDataSource = virtualDataSourceRoute.GetAllDataSourceNames().Contains(dataSourceName);
if (isShardingDataSource)
{
migrationResult.InDataSource = true;
}
else
{
migrationResult.InDataSource = false;
}
}
else
{
migrationResult.InDataSource = defaultDataSourceName == dataSourceName;
}
//分表
if (migrationResult.InDataSource && !string.IsNullOrWhiteSpace(absTableName) &&
existsShardingTables.ContainsKey(absTableName))
{
var shardings = existsShardingTables[absTableName];
shardings.ForEach(aShardingTable =>
{
string newCmd = sourceCmd;
var replaceGroups = GetReplaceGroups(operation, absTableName, aShardingTable);
foreach (var migrationReplaceItem in replaceGroups)
{
var delimitSourceNameIdentifier =
sqlGenerationHelper.DelimitIdentifier(migrationReplaceItem.SourceName);
var delimitTargetNameIdentifier =
sqlGenerationHelper.DelimitIdentifier(migrationReplaceItem.TargetName);
newCmd = newCmd.Replace(
delimitSourceNameIdentifier,
delimitTargetNameIdentifier);
}
if (newCmd.Contains(
"EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE'"))
{
newCmd = newCmd.Replace(
$"EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'{absTableName}'",
$"EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'{aShardingTable}'");
}
resList.Add(newCmd);
});
}
}
return (migrationResult, resList);
string BuildPattern(string absTableName)
{
return string.Format("^({0})$|^({0}_.*?)$|^(.*?_{0}_.*?)$|^(.*?_{0})$", absTableName);
}
}
private static ISet<MigrationReplaceItem> GetReplaceGroups(
MigrationOperation operation, string sourceTableName, string targetTableName)
{
ISet<MigrationReplaceItem> resList =
new HashSet<MigrationReplaceItem>()
{
new MigrationReplaceItem(sourceTableName, targetTableName)
};
string name = operation.GetPropertyValue("Name") as string;
if (!string.IsNullOrWhiteSpace(name))
{
if (!(operation is ColumnOperation columnOperation))
{
string[] patterns = new string[]
{
$"^()({sourceTableName})()$", $"^()({sourceTableName})(_.*?)$",
$"^(.*?_)({sourceTableName})(_.*?)$", $"^(.*?_)({sourceTableName})()$"
};
foreach (var aPattern in patterns)
{
if (Regex.IsMatch(name, aPattern))
{
var newName = new Regex(aPattern).Replace(name, "${1}" + targetTableName + "$3");
resList.Add(new MigrationReplaceItem(name, newName));
break;
}
}
}
}
Func<PropertyInfo, bool> listPropertyWhere = x =>
x.PropertyType.IsGenericType
&& x.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
&& typeof(MigrationOperation).IsAssignableFrom(x.PropertyType.GetGenericArguments()[0]);
//其它
var propertyInfos = operation.GetType().GetProperties()
.Where(x => x.Name != "Name"
&& x.Name != "Table"
&& x.PropertyType != typeof(object)
&& (typeof(MigrationOperation).IsAssignableFrom(x.PropertyType) || listPropertyWhere(x))
)
.ToList();
propertyInfos
.ForEach(aProperty =>
{
var propertyValue = aProperty.GetValue(operation);
if (propertyValue is MigrationOperation propertyOperation)
{
var migrationReplaceItems = GetReplaceGroups(propertyOperation, sourceTableName, targetTableName);
foreach (var migrationReplaceItem in migrationReplaceItems)
{
resList.Add(migrationReplaceItem);
}
}
else if (listPropertyWhere(aProperty))
{
foreach (var aValue in (IEnumerable)propertyValue)
{
var migrationReplaceItems = GetReplaceGroups((MigrationOperation)aValue, sourceTableName,
targetTableName);
foreach (var migrationReplaceItem in migrationReplaceItems)
{
resList.Add(migrationReplaceItem);
}
}
}
});
return resList;
}
}

View File

@ -0,0 +1,9 @@
namespace Sample.OracleIssue;
public class Program
{
public static void Main(string[] args)
{
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Oracle.EntityFrameworkCore" Version="6.21.61" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ShardingCore\ShardingCore.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Oracle.EntityFrameworkCore.Infrastructure.Internal;
using Oracle.EntityFrameworkCore.Migrations;
using ShardingCore.Core.RuntimeContexts;
using ShardingCore.Helpers;
namespace Sample.OracleIssue;
public class ShardingOracleMigrationsSqlGenerator:OracleMigrationsSqlGenerator
{
private readonly IShardingRuntimeContext _shardingRuntimeContext;
public ShardingOracleMigrationsSqlGenerator(IShardingRuntimeContext shardingRuntimeContext,MigrationsSqlGeneratorDependencies dependencies, IOracleOptions options) : base(dependencies, options)
{
_shardingRuntimeContext = shardingRuntimeContext;
}
protected override void Generate(MigrationOperation operation, IModel? model, MigrationCommandListBuilder builder)
{
var oldCmds = builder.GetCommandList().ToList();
base.Generate(operation, model, builder);
var newCmds = builder.GetCommandList().ToList();
var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList();
//oracle需要自行重写因为和其他的数据库不一样并不是使用了基类来处理
OracleMigrationHelper.Generate(_shardingRuntimeContext,operation, builder, Dependencies.SqlGenerationHelper, addCmds);
}
}

View File

@ -4,10 +4,10 @@ using Microsoft.Extensions.DependencyInjection;
namespace ShardingCore.Core.ServiceProviders
{
public class ShardingProvider:IShardingProvider
public sealed class ShardingProvider:IShardingProvider
{
private readonly IServiceProvider _internalServiceProvider;
private IServiceProvider _applicationServiceProvider;
private readonly IServiceProvider _applicationServiceProvider;
public ShardingProvider(IServiceProvider internalServiceProvider,IServiceProvider applicationServiceProvider)
{

View File

@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace ShardingCore.Core.ServiceProviders
{
public class ShardingScope:IShardingScope
public sealed class ShardingScope:IShardingScope
{
private readonly IServiceScope _internalServiceScope;
private readonly IServiceScope _applicationServiceScope;