这个答案有两个部分;首先需要在IdentityServer的配置中调整表名,以便它使用新的表名生成查询。其次,需要修改由实体框架生成的模式,以便它知道为Identity Framework实体创建不同命名的表。请继续阅读...
首先,可以通过AddOperationalStore
和AddConfigurationStore
方法来更改Entity Framework查询中使用的表名,这些方法挂在AddIdentityServer
中间件方法上。提供给配置方法的委托的options
参数公开了表名,例如:options.{EntityName}.Name = {WhateverTableNameYouWantToUse}
- 或者 options.ApiResource.Name = mytesttable
。您还可以通过调整Schema
属性来针对每个表覆盖模式。
下面的示例使用反射更新所有实体,以使用以idn_
为前缀的表名,因此为idn_ApiResources
、idn_ApiScopes
等:
services.AddIdentityServer()
.AddConfigurationStore(options => {
// Loop through and rename each table to 'idn_{tablename}' - E.g. `idn_ApiResources`
foreach(var p in options.GetType().GetProperties()) {
if (p.PropertyType == typeof(IdentityServer4.EntityFramework.Options.TableConfiguration))
{
object o = p.GetGetMethod().Invoke(options, null);
PropertyInfo q = o.GetType().GetProperty("Name");
string tableName = q.GetMethod.Invoke(o, null) as string;
o.GetType().GetProperty("Name").SetMethod.Invoke(o, new object[] { $"idn_{tableName}" });
}
}
// Configure DB Context connection string and migrations assembly where migrations are stored
options.ConfigureDbContext = builder => builder.UseNpgsql(_configuration.GetConnectionString("IDPDataDBConnectionString"),
sql => sql.MigrationsAssembly(typeof(IdentityServer.Data.DbContexts.MyTestDbContext).GetTypeInfo().Assembly.GetName().Name));
}
.AddOperationalStore(options => {
// Copy and paste from AddConfigurationStore logic above.
}
第二部分是修改由实体框架生成的IdentityServer实体所生成的模式。为了完成此操作,您有两个选择;您可以从IdentityServer提供的ConfigurationDbContext
或PersistedGrantDbContext
中派生出一个,然后覆盖OnModelCreating
方法以将每个IdentityServer实体重新映射到修改后的表名,然后创建您的初始迁移或更新迁移,如此处记载(使用流利API语法)或者您可以根据教程添加迁移部分从提供的IdentityServer DBContext的ConfigurationDbContext
和PersistedGrantDbContext
创建初始迁移,然后仅在所有表名和这些表名的引用上使用文本编辑器进行查找和替换在已创建的迁移文件中。
无论您选择哪种方法,仍需要使用
dotnet ef migrations ...
命令行语法来创建初始迁移文件(如
添加迁移所示)或带有表更改的修改集,完成后运行IdentityServer项目,模式将在目标数据库中创建。
注意:通过
dotnet ef migrations
语法(也称为设计时)调用
OnModelCreating
,如果您在DBContext上调用
Database.Migrate()
,也会在运行时调用 - 例如:
MyDbContextInstance.Database.Migrate()
(或异步等效方法)。
如果要使用自定义DBContext以便可以自定义
OnModelCreating
,则需要添加一些设计时类,这些类在从命令行调用
dotnet ef
时使用,并将新上下文添加到
Startup
。
为了完整起见,以下是一个hacky的粗略示例,其中上下文目标是PostGres数据库(如果不同,请使用
UseNpgsql
或其他后备存储代替
UseSQLServer
),连接字符串名称为
IDPDataDBConnectionString
在appsettings.json文件中,此示例中的自定义DB上下文是从IdentityServer的
ConfigurationDbContext
派生的
MyTestDbContext
。
复制并粘贴代码,调整到appsettings.json
的路径(或重构),然后从命令行执行dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c MyTestDbContext -o Data/Migrations/IdentityServer/ConfigurationDbCreatedWithMyTestContext
,您应该看到Entity Framework使用您放置在派生上下文的OnModelCreating
中的任何覆盖生成模式迁移文件。下面的示例还包括一些Console.WriteLine
调用,以便更轻松地跟踪正在发生的情况。
将此添加到Startup
:
services.AddDbContext<MyTestDbContext>(options =>
{
options.UseNpgsql(_configuration.GetConnectionString("IDPDataDBConnectionString"));
});
请注意,设计时类的使用还允许您将IdentityServer数据库迁移文件分离到单独的类库中(如果您愿意)。如果您这样做,请确保在
Startup
中针对它进行目标设置(有关更多信息,请参见
此处)。
namespace MyIdentityServer.DataClassLibrary.DbContexts
{
public class MyTestDbContext : ConfigurationDbContext
{
public MyTestDbContext(DbContextOptions<ConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options, storeOptions)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Console.WriteLine("OnModelCreating invoking...");
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityServer4.EntityFramework.Entities.ApiResource>().ToTable("mytesttable");
Console.WriteLine("...OnModelCreating invoked");
}
}
public class MyTestContextDesignTimeFactory : DesignTimeDbContextFactoryBase<MyTestDbContext>
{
public MyTestContextDesignTimeFactory()
: base("IDPDataDBConnectionString", typeof(MyTestContextDesignTimeFactory).GetTypeInfo().Assembly.GetName().Name)
{
}
protected override MyTestDbContext CreateNewInstance(DbContextOptions<MyTestDbContext> options)
{
var x = new DbContextOptions<ConfigurationDbContext>();
Console.WriteLine("Here we go...");
var optionsBuilder = newDbContextOptionsBuilder<ConfigurationDbContext>();
optionsBuilder.UseNpgsql("IDPDataDBConnectionString", postGresOptions => postGresOptions.MigrationsAssembly(typeof(MyTestContextDesignTimeFactory).GetTypeInfo().Assembly.GetName().Name));
DbContextOptions<ConfigurationDbContext> ops = optionsBuilder.Options;
return new MyTestDbContext(ops, new ConfigurationStoreOptions());
}
}
public abstract class DesignTimeDbContextFactoryBase<TContext> :
IDesignTimeDbContextFactory<TContext> where TContext : DbContext
{
protected string ConnectionStringName { get; }
protected String MigrationsAssemblyName { get; }
public DesignTimeDbContextFactoryBase(string connectionStringName, string migrationsAssemblyName)
{
ConnectionStringName = connectionStringName;
MigrationsAssemblyName = migrationsAssemblyName;
}
public TContext CreateDbContext(string[] args)
{
return Create(
Directory.GetCurrentDirectory(),
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
ConnectionStringName, MigrationsAssemblyName);
}
protected abstract TContext CreateNewInstance(
DbContextOptions<TContext> options);
public TContext CreateWithConnectionStringName(string connectionStringName, string migrationsAssemblyName)
{
var environmentName =
Environment.GetEnvironmentVariable(
"ASPNETCORE_ENVIRONMENT");
var basePath = AppContext.BaseDirectory;
return Create(basePath, environmentName, connectionStringName, migrationsAssemblyName);
}
private TContext Create(string basePath, string environmentName, string connectionStringName, string migrationsAssemblyName)
{
var builder = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile(@"c:\change\this\path\to\appsettings.json")
.AddJsonFile($"appsettings.{environmentName}.json", true)
.AddEnvironmentVariables();
var config = builder.Build();
var connstr = config.GetConnectionString(connectionStringName);
if (String.IsNullOrWhiteSpace(connstr) == true)
{
throw new InvalidOperationException(
"Could not find a connection string named 'default'.");
}
else
{
return CreateWithConnectionString(connstr, migrationsAssemblyName);
}
}
private TContext CreateWithConnectionString(string connectionString, string migrationsAssemblyName)
{
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentException(
$"{nameof(connectionString)} is null or empty.",
nameof(connectionString));
var optionsBuilder =
new DbContextOptionsBuilder<TContext>();
Console.WriteLine(
"MyDesignTimeDbContextFactory.Create(string): Connection string: {0}",
connectionString);
optionsBuilder.UseNpgsql(connectionString, postGresOptions => postGresOptions.MigrationsAssembly(migrationsAssemblyName));
DbContextOptions<TContext> options = optionsBuilder.Options;
Console.WriteLine("Instancing....");
return CreateNewInstance(options);
}
}
}
顺便说一下;如果你已经有一个包含IdentityServer表的数据库,你可以手动重命名它们,忽略Entity Framework迁移 - 然后你只需要在Startup
中进行AddConfigurationStore
和AddOperationalStore
的更改。
Startup
中返回的对象上公开的AddIdentityServer
的AddConfigurationStore
和AddOperationalStore
配置方法中更改实体目标的表名。所以;services.AddIdentityServer().AddConfigurationStore(options => { options.ApiResource.Name = "mytesttable"; })
- 所有IdentityServer实体->表名映射都是通过这种方式公开的。还需要在DBContexts的OnModelCreating
中添加必要的ToTable()
调用,这样就可以完美地使用与它们支持的实体不同的表名了。 - Yumbelie