更改IdentityServer4实体框架表名称

时间:2018-07-23 16:58:55

标签: c# postgresql entity-framework entity-framework-core identityserver4

我试图更改由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的{​​{1}} 。但是,当我从IdentityServer实例调用命令时,生成的SQL仍引用IdentityServer4.EntityFramework.Entities.ApiResource而不是ApiResources

mytesttable

任何帮助表示赞赏。

1 个答案:

答案 0 :(得分:2)

此答案分为两部分;首先,需要在IdentityServer的配置中调整表名,以便它使用新的表名生成查询。其次;实体框架生成的架构需要进行修改,以便它知道为Identity Framework实体创建名称不同的表。继续阅读...

因此,首先; AddOperationalStoreAddConfigurationStore方法(与AddIdentityServer中间件方法无关的方法)具有更改Entity Framework查询中使用的表名的功能。提供给配置方法的委托的options参数公开表名,例如:options.{EntityName}.Name = {WhateverTableNameYouWantToUse}-或options.ApiResource.Name = mytesttable。您还可以通过调整Schema属性来逐表覆盖架构。

下面的示例使用反射来更新所有实体,以使用前缀为idn_的表名,因此idn_ApiResourcesidn_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之一中派生; ConfigurationDbContextPeristedGrantDbContext,然后重写OnModelCreating方法以将每个IdentityServer实体重新映射到修改后的表名,然后以documented here创建初始迁移或更新的迁移(Fluent Api语法),,您可以按照教程Adding Migrations部分从提供的IdentityServer DBContext的ConfigurationDbContextPersistedGrantDbContext创建初始迁移,然后进行查找并在所有表名上使用文本编辑器替换,并在创建的迁移文件中引用这些表名。

无论选择哪种方法,您仍然需要使用dotnet ef migrations ...命令行语法来创建Adding Migrations中所示的初始迁移文件,或者创建具有表更改的修改集,完成后为此,运行您的IdentityServer项目,然后将在目标数据库中创建架构。

注意; OnModelCreating是通过dotnet ef migrations语法(在设计时)调用的,也可以在运行时调用(如果您在DBContext上调用Database.Migrate()的话),例如MyDbContextInstance.Database.Migrate()(或异步等效方法)。

如果要使用自定义DBContext以便自定义OnModelCreating,则需要添加一些设计时类,这些类在从命令行调用dotnet ef并添加新的上下文时使用到Startup

为完整起见,下面是一个粗糙的粗略示例,其中上下文目标是PostGres数据库(使用UseSQLServer代替UseNpgsql或其他任何后备存储,如果它不同)和连接appsettings.json文件中的字符串名称为IDPDataDBConnectionString,在这种情况下,自定义数据库上下文为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和{{ 1}}。