C# 更改IdentityServer4实体框架表名称
我正在尝试更改PersistedGrantDb和ConfigurationDb为IdentityServer4创建的默认表名,并让实体框架生成正确的SQL。例如我不使用使用表C# 更改IdentityServer4实体框架表名称,c#,postgresql,entity-framework,entity-framework-core,identityserver4,C#,Postgresql,Entity Framework,Entity Framework Core,Identityserver4,我正在尝试更改PersistedGrantDb和ConfigurationDb为IdentityServer4创建的默认表名,并让实体框架生成正确的SQL。例如我不使用使用表ApiResources的实体IdentityServer4.EntityFramework.Entities.ApiResource,而是希望将数据映射到名为mytesttable 根据定义,这应该很简单,只需在DBContext的OnModelCreating方法中为我想要重新映射的每个实体添加ToTable调用,以覆盖
ApiResources
的实体IdentityServer4.EntityFramework.Entities.ApiResource
,而是希望将数据映射到名为mytesttable
根据定义,这应该很简单,只需在DBContext的
OnModelCreating
方法中为我想要重新映射的每个实体添加ToTable
调用,以覆盖TableName=EntityName的默认行为。问题是,这确实创建了一个表mytesttable
,但实体框架在运行时创建的SQL仍然在查询中使用ApiResources
,因此失败
我采取的步骤是创建了一个从IdentityServer的ConfigurationDbContext
派生的DBContext
,以便能够覆盖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");
}
}
这将很高兴地使用NpgSQL
提供程序在我的PostGRES数据库中漫游并创建必要的表,包括名为mytesttable
的表,代替实体IdentityServer4.EntityFramework.Entities.ApiResource
的ApiResources。但是,当我从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的配置中调整表名,以便它使用新表名生成查询。其次需要修改实体框架生成的模式,以便它知道如何为Identity framework实体创建不同名称的表。继续读
所以,首先,;更改实体框架查询中使用的表名的功能在挂起AddIdentityServer
中间件方法的AddOperationalStore
和AddConfigurationStore
方法上公开。提供给配置方法的委托的options
参数公开表名,例如:options.{EntityName}.Name={WhateverTableNameYouWantToUse}
-或options.ApiResource.Name=mytesttable
。您还可以通过调整schema
属性,在每个表的基础上重写架构
下面的示例使用反射更新所有实体,以使用前缀为idn\u
的表名,因此idn\u ApiResources
,idn\u 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提供的DBContext之一派生ConfigurationDbContext
或PeristedGrandDBContext
,然后重写OnModelCreating
方法,将每个IdentityServer实体重新映射到修改后的表名,然后创建初始迁移或更新的迁移(流畅的Api语法),或您可以根据教程部分的说明,从提供的IdentityServer DBContext的ConfigurationDbContext
和PersistedGrandDBContext
创建初始迁移,然后只需在创建的迁移文件中的所有表名和对这些表名的引用上使用文本编辑器进行查找和替换
无论选择哪种方法,您仍然需要使用dotnet ef migrations…
命令行语法来创建中所示的初始迁移文件或表更改的修改集,完成后,运行IdentityServer项目,并在目标数据库中创建模式
注OnModelCreating
通过dotnet ef migrations
语法(即在设计时)调用,如果在DBContext上调用Database.Migrate()
,也可以在运行时调用,例如MyDbContextInstance.Database.Migrate()
(或异步等效方法)
如果要使用自定义DBContext以便可以自定义OnModelCreating
,则需要添加一些设计时类,这些类在从命令行调用dotnet ef
时使用,并将新上下文添加到启动
为了完整起见,下面是一个粗略的示例,其中上下文目标是PostGres数据库(使用UseSQLServer
代替UseNpgsql
或任何与之不同的备份存储)appsettings.json文件中的连接字符串名称是IDPDataDBConnectionString
,本例中的自定义数据库上下文是MyTestDbContext
,它源自IdentityServer的ConfigurationDbContext
复制并粘贴代码,将路径调整为appsettings.json
(或重构)然后从命令行executedotnet ef migrations添加InitialIdentityServerConfigurationDbMigration-c MyTestDbContext-o Data/migrations/IdentityServer/ConfigurationDbCreatedWithMyTestContext
,您应该看到实体框架使用您在其中放置的任何覆盖生成架构迁移文件在派生上下文上创建模型。下面的示例还包括一些Console.WriteLine
调用,以便更轻松地跟踪正在发生的事情
将此添加到启动:
services.AddDbContext<MyTestDbContext>(options =>
{
options.UseNpgsql(_configuration.GetConnectionString("IDPDataDBConnectionString"));
});
旁注;如果您已经有了一个包含IdentityServer表的数据库,您可以忽略EntityFrameworks迁移手动重命名它们-然后您只需要将Startup
更改为AddConfigurationStore
和AddOperationalSto
services.AddDbContext<MyTestDbContext>(options =>
{
options.UseNpgsql(_configuration.GetConnectionString("IDPDataDBConnectionString"));
});
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);
}
}
}