我正在尝试更改由 PersistedGrantDb 和 ConfigurationDb 为 IdentityServer4 创建的默认表名,并让 Entity Framework 生成正确的 SQL。例如;而不是使用实体 IdentityServer4.EntityFramework.Entities.ApiResource
使用表格 ApiResources
,我希望将数据映射到名为 mytesttable
的表中
根据documentation这应该像添加 ToTable
一样简单我想在 DBContext's
中重新映射的每个实体的调用OnModelCreating
方法来覆盖 TableName = EntityName 的默认行为。问题是这确实创建了一个表 mytesttable
但是 Entity Framework 在运行时创建的 SQL 仍然使用 ApiResources
在查询中,因此失败。
我采取的步骤是创建了一个 DBContext
派生自 IdentityServer 的 ConfigurationDbContext
为了能够覆盖OnModelCreating
并自定义表名:
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");
base.OnModelCreating(modelBuilder);
Console.WriteLine("...OnModelCreating invoked");
}
}
我还实现了
DesignTimeDbContextFactoryBase<MyTestDBContext>
类制造MyTestDbContext
在设计时通过 dotnet ef migrations
调用的实例命令行语法。这工作和调用
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c MyTestDbContext -o Data/Migrations/IdentityServer/MyTestContext
在我的程序集中创建初始迁移。然后我启动 IdentityServer 实例,从
Startup
调用一个测试方法。其中包含以下逻辑:private static void InitalizeDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
var context = serviceScope.ServiceProvider.GetRequiredService<MyTestDbContext>();
context.Database.Migrate();
/* Add some test data here... */
}
}
这愉快地漫游并使用
NpgSQL
在我的 PostGRES 数据库中创建必要的表提供者,包括名为 mytesttable
的表代替ApiResources
对于实体 IdentityServer4.EntityFramework.Entities.ApiResource
.但是,当我从 IdentityServer 实例调用命令时,生成的 SQL 仍然引用 ApiResources
而不是 mytesttable
: Failed executing DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT x."Id", x."Description", x."DisplayName", x."Enabled", x."Name"
FROM "ApiResources" AS x
ORDER BY x."Id"
Npgsql.PostgresException (0x80004005): 42P01: relation "ApiResources" does not exist
任何帮助表示赞赏。
最佳答案
这个答案有两个部分;首先需要在 IdentityServer 的配置中调整表名,以便它使用新的表名生成查询。其次; Entity Framework 生成的模式需要修改,以便它知道为身份框架实体创建不同命名的表。继续阅读...
所以,首先; AddOperationalStore
公开了更改 Entity Framework 查询中使用的表名的能力。和 AddConfigurationStore
挂断 AddIdentityServer
的方法中间件方法。 options
提供给配置方法的委托(delegate)参数公开表名,例如:options.{EntityName}.Name = {WhateverTableNameYouWantToUse}
- 或 options.ApiResource.Name = mytesttable
.您也可以通过调整 Schema
来覆盖每个表的架构。属性(property)。
下面的示例使用反射来更新所有实体以使用以 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.
}
第二部分是修改 Entity Framework 从 IdentityServer 实体生成的模式。要做到这一点,你有两个选择;您可以从 IdentityServer 提供的 DBContext 之一派生;
ConfigurationDbContext
或 PeristedGrantDbContext
然后覆盖 OnModelCreating
方法将每个 IdentityServer 实体重新映射到修改后的表名,然后将初始迁移或更新迁移创建为 documented here (流利的 Api 语法),或 您可以从提供的 IdentityServer DBContext 的 ConfigurationDbContext
创建初始迁移和 PersistedGrantDbContext
根据教程 Adding Migrations部分,然后在创建的迁移文件中对所有表名和对这些表名的引用使用文本编辑器进行查找和替换。无论您选择哪种方法,您仍然需要使用
dotnet ef migrations ...
用于创建初始迁移文件的命令行语法,如 Adding Migrations 所示或带有表更改的修改集,一旦您完成此操作,运行您的 IdentityServer 项目,架构将在目标数据库中创建。笔记;
OnModelCreating
通过 dotnet ef migrations
调用语法(又名在设计时)以及运行时,如果您调用 Database.Migrate()
在您的 DBContext 上 - 例如MyDbContextInstance.Database.Migrate()
(或异步等效方法)。如果您想使用自定义 DBContext 以便您可以自定义
OnModelCreating
,您需要添加一些在调用 dotnet ef
时使用的设计时类从命令行添加新的上下文到 Startup
.为了完整起见,下面是一个粗略的例子,其中上下文目标是 PostGres 数据库(使用
UseSQLServer
代替 UseNpgsql
或任何你的后备存储,如果它不同的话)并且连接字符串名称是 IDPDataDBConnectionString
在 appsettings.json 文件中,在这种情况下自定义数据库上下文是 MyTestDbContext
它源自 IdentityServer 的 ConfigurationDbContext
.复制粘贴代码,调整路径为
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
如果您这样做(参见 here 了解更多信息)。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);
// Map the entities to different tables here
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());
}
}
/* Enable these if you just want to host your data migrations in a separate assembly and use the IdentityServer supplied DbContexts
public class ConfigurationContextDesignTimeFactory : DesignTimeDbContextFactoryBase<ConfigurationDbContext>
{
public ConfigurationContextDesignTimeFactory()
: base("IDPDataDBConnectionString", typeof(ConfigurationContextDesignTimeFactory).GetTypeInfo().Assembly.GetName().Name)
{
}
protected override ConfigurationDbContext CreateNewInstance(DbContextOptions<ConfigurationDbContext> options)
{
return new ConfigurationDbContext(options, new ConfigurationStoreOptions());
}
}
public class PersistedGrantContextDesignTimeFactory : DesignTimeDbContextFactoryBase<PersistedGrantDbContext>
{
public PersistedGrantContextDesignTimeFactory()
: base("IDPDataDBConnectionString", typeof(PersistedGrantContextDesignTimeFactory).GetTypeInfo().Assembly.GetName().Name)
{
}
protected override PersistedGrantDbContext CreateNewInstance(DbContextOptions<PersistedGrantDbContext> options)
{
return new PersistedGrantDbContext(options, new OperationalStoreOptions());
}
}
*/
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 表的数据库,您可以手动重命名它们而忽略 EntityFrameworks 迁移 - 您唯一需要的是
Startup
中的更改至AddConfigurationStore
和 AddOperationalStore
.
关于c# - 更改 IdentityServer4 Entity Framework 表名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51483885/