Sto cercando di cambiare i nomi delle tabelle predefinite create da PersistedGrantDb e ConfigurationDb per IdentityServer4 e di avere Entity Framework che genera l'SQL corretto. Per esempio; invece di utilizzare l'entità IdentityServer4.EntityFramework.Entities.ApiResource
utilizzando la tabella ApiResources
, voglio che i dati vengano mappati in una tabella denominata mytesttable
Secondo la documentazione questo dovrebbe essere semplice come aggiungere invocazioni ToTable
per ogni entità che voglio rimappare nel metodo DBContext's
OnModelCreating
per sovrascrivere il comportamento predefinito di TableName = EntityName. Il problema è che questo effettivamente crea una tabella mytesttable
ma l'SQL creato da Entity Framework in runtime utilizza ancora ApiResources
nella query e di conseguenza fallisce.
I passaggi che ho preso sono che ho creato un DBContext
che deriva da ConfigurationDbContext
di IdentityServer per poter eseguire l'override di OnModelCreating
e personalizzare i nomi delle tabelle:
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");
}
}
Ho anche implementato una DesignTimeDbContextFactoryBase<MyTestDBContext>
per MyTestDbContext
istanza MyTestDbContext
quando richiamata in fase di progettazione tramite la sintassi della riga di comando dotnet ef migrations
.
Questo funziona e un'invocazione delle dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c MyTestDbContext -o Data/Migrations/IdentityServer/MyTestContext
di dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c MyTestDbContext -o Data/Migrations/IdentityServer/MyTestContext
crea le migrazioni iniziali nel mio assembly.
Quindi avvio l'istanza IdentityServer, richiamando un metodo di prova da Startup
che contiene la seguente logica:
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... */
}
}
e questo felicemente vaga e crea le tabelle necessarie nel mio database PostGRES usando il provider NpgSQL
, inclusa la tabella denominata mytesttable
al posto di ApiResources
per l'entità IdentityServer4.EntityFramework.Entities.ApiResource
. Tuttavia, quando invoco un comando ApiResources
IdentityServer, l'SQL che viene generato fa ancora riferimento a ApiResources
anziché a 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
Qualsiasi assistenza apprezzata.
Ci sono due parti per questa risposta; in primo luogo, i nomi delle tabelle devono essere adattati nella configurazione di IdentityServer in modo che generi query utilizzando i nuovi nomi di tabella. in secondo luogo; lo schema generato dal framework di entità deve essere modificato in modo che sappia creare le tabelle con nomi diversi per le entità di Identity Framework. Continuare a leggere...
Quindi, prima su; la possibilità di modificare i nomi tabella utilizzati nelle query Entity Framework viene esposta sui metodi AddOperationalStore
e AddConfigurationStore
che si AddIdentityServer
metodo middleware AddIdentityServer
. L'argomento delle options
del delegato fornito ai metodi di configurazione espone i nomi delle tabelle, ad esempio: options.{EntityName}.Name = {WhateverTableNameYouWantToUse}
- o options.ApiResource.Name = mytesttable
. È anche possibile sovrascrivere lo schema su una base per tabella regolando la proprietà dello Schema
.
L'esempio seguente utilizza la reflection per aggiornare tutte le entrate per utilizzare i nomi delle tabelle con prefisso idn_
, quindi idn_ApiResources
, idn_ApiScopes
etc:
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.
}
La seconda parte consiste nella modifica dello schema generato dal framework entità dalle entità IdentityServer. Per realizzare questo hai due scelte; puoi derivare da uno dei DBContexts forniti da IdentityServer; ConfigurationDbContext
o PeristedGrantDbContext
e quindi eseguire l'override del metodo OnModelCreating
per rimappare ogni entità IdentityServer al nome della tabella modificata e quindi creare la migrazione iniziale o la migrazione aggiornata come documentato qui (sintassi di Fluent Api), oppure è possibile creare la migrazione iniziale dal ConfigurationDbContext
di DBContext fornito da IdentityServer. e PersistedGrantDbContext
come da tutorial Aggiungere sezione Migrazioni , quindi fare una ricerca e sostituirla con un editor di testo su tutti i nomi di tabelle e riferimenti a quei nomi di tabelle nei file di migrazione creati.
Indipendentemente dal metodo scelto, dovrai comunque utilizzare le dotnet ef migrations ...
sintassi della riga di comando per creare i file di migrazione iniziali come mostrato in Aggiunta di migrazioni o un set modificato con le modifiche alla tabella e, una volta fatto, esegui IdentityServer progetto e lo schema verrà creato nel database di destinazione.
Nota; OnModelCreating
viene richiamato tramite la sintassi di dotnet ef migrations
(aka in Design Time) e anche in runtime se si chiama Database.Migrate()
sul proprio DBContext, ad esempio MyDbContextInstance.Database.Migrate()
(o il metodo equivalente asincrono).
Se si desidera utilizzare un DBContext personalizzato in modo da poter personalizzare OnModelCreating
, è necessario aggiungere alcune classi temporali di progettazione che vengono utilizzate quando si chiama dotnet ef
dalla riga di comando e si aggiunge il nuovo contesto Startup
.
Per completezza di seguito è riportato un esempio approssimativo di UseSQLServer
in cui il target di contesto è un database PostGres (utilizzare UseSQLServer
al posto di UseNpgsql
o qualunque sia il backing store se differisce) e il nome della stringa di connessione è IDPDataDBConnectionString
nel file appsettings.json e il contesto DB personalizzato in questo caso è MyTestDbContext
che deriva da ConfigurationDbContext
di IdentityServer.
Copia e incolla il codice, aggiusta il percorso a appsettings.json
(o refactor) e poi dalla riga di comando esegui dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c MyTestDbContext -o Data/Migrations/IdentityServer/ConfigurationDbCreatedWithMyTestContext
e dovresti vedere Entity Framework generare il file di migrazione dello schema utilizzando qualsiasi override che hai inserito in OnModelCreating
sul tuo contesto derivato. L'esempio di seguito include anche alcune chiamate di Console.WriteLine
per rendere più facile tracciare cosa sta succedendo.
Aggiungi questo Startup
:
services.AddDbContext<MyTestDbContext>(options =>
{
options.UseNpgsql(_configuration.GetConnectionString("IDPDataDBConnectionString"));
});
Si noti che l'uso delle classi temporali di progettazione consente anche di separare i file di migrazione del database IdentityServer in una libreria di classi separata, se lo si desidera. Assicurati di mirare in Startup
se lo fai (Vedi qui per maggiori informazioni).
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);
}
}
}
Nota a margine; Se hai già un database con le tabelle IdentityServer, puoi semplicemente rinominarle ignorando manualmente le migrazioni di EntityFrameworks: l'unico bit di cui avrai bisogno sono le modifiche in Startup
a AddConfigurationStore
e AddOperationalStore
.