更改 IdentityServer4 实体框架表名称

2024-05-09

我正在尝试更改由 IdentityServer4 的 PersistedGrantDb 和 ConfigurationDb 创建的默认表名称,并让实体框架生成正确的 SQL。例如;而不是使用实体IdentityServer4.EntityFramework.Entities.ApiResource使用表格ApiResources,我希望将数据映射到名为的表中mytesttable

根据文档 https://learn.microsoft.com/en-us/ef/core/modeling/relational/tables这应该像添加一样简单ToTable对我想要在中重新映射的每个实体的调用DBContext's OnModelCreating方法来覆盖 TableName = EntityName 的默认行为。问题是这确实创建了一个表mytesttable但实体框架在运行时创建的 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... */
        }
}

这愉快地漫游并使用在我的 PostGRES 数据库中创建必要的表NpgSQL提供者,包括名为的表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 的配置中调整表名称,以便它使用新的表名称生成查询。第二;需要修改实体框架生成的模式,以便它知道为身份框架实体创建不同名称的表。继续阅读...

所以,首先;更改实体框架查询中使用的表名称的能力公开在AddOperationalStore and AddConfigurationStore挂起的方法AddIdentityServer中间件方法。这options提供给配置方法的委托参数公开表名称,例如:options.{EntityName}.Name = {WhateverTableNameYouWantToUse} - or options.ApiResource.Name = mytesttable。您还可以通过调整每个表的基础来覆盖架构Schema财产。

下面的示例使用反射来更新所有实体以使用前缀为的表名idn_, so 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.
}

第二部分是修改实体框架从 IdentityServer 实体生成的模式。要实现这一目标,您有两个选择:您可以从 IdentityServer 提供的 DBContext 之一派生;ConfigurationDbContext or PeristedGrantDbContext然后覆盖OnModelCreating方法将每个 IdentityServer 实体重新映射到修改后的表名称,然后创建初始迁移或更新迁移为记录在这里 https://learn.microsoft.com/en-us/ef/core/modeling/relational/tables(流利的Api语法),or您可以从提供的 IdentityServer DBContext 创建初始迁移ConfigurationDbContext and PersistedGrantDbContext按照教程添加迁移 https://identityserver4.readthedocs.io/en/release/quickstarts/8_entity_framework.html部分,然后使用文本编辑器对所有表名称以及对创建的迁移文件中这些表名称的引用进行查找和替换。

无论您选择哪种方法,您仍然需要使用dotnet ef migrations ...命令行语法用于创建初始迁移文件,如下所示添加迁移 https://identityserver4.readthedocs.io/en/release/quickstarts/8_entity_framework.html或包含表更改的修改集,完成此操作后,运行 IdentityServer 项目,将在目标数据库中创建架构。

Note; OnModelCreating是通过调用dotnet ef migrations语法(也称为设计时)以及运行时(如果您调用)Database.Migrate()在你的 DBContext 上 - 例如MyDbContextInstance.Database.Migrate()(或异步等效方法)。

如果你想使用自定义的DBContext,那么你可以自定义OnModelCreating,您需要添加一些在调用时使用的设计时类dotnet ef从命令行并将新上下文添加到Startup.

为了完整起见,下面是一个 hacky 粗略示例,其中上下文目标是 PostGres 数据库(使用UseSQLServer代替UseNpgsql或者无论您的后备存储是什么(如果不同),连接字符串名称是IDPDataDBConnectionString在 appsettings.json 文件和本例中的自定义数据库上下文中是MyTestDbContext它源自 IdentityServer 的ConfigurationDbContext.

复制粘贴代码,调整路径为appsettings.json(或重构)然后从命令行执行dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c MyTestDbContext -o Data/Migrations/IdentityServer/ConfigurationDbCreatedWithMyTestContext您应该看到实体框架使用您放置的任何覆盖生成架构迁移文件OnModelCreating根据您派生的上下文。下面的例子还包括一些Console.WriteLine调用以更轻松地跟踪正在发生的事情。

将其添加到Startup:

 services.AddDbContext<MyTestDbContext>(options =>
        {
            options.UseNpgsql(_configuration.GetConnectionString("IDPDataDBConnectionString"));
        }); 

Note如果您愿意,使用设计时类还允许您将 IdentityServer 数据库迁移文件分离到单独的类库中。确保您的目标是Startup如果你这样做(参见here https://stackoverflow.com/questions/38705694/add-migration-with-different-assembly了解更多信息)。

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 表的数据库,您可以手动重命名它们,忽略实体框架迁移 - 您唯一需要的就是更改Startup to AddConfigurationStore and AddOperationalStore.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

更改 IdentityServer4 实体框架表名称 的相关文章

  • C# 异步等待澄清?

    我读了here http blog stephencleary com 2012 02 async and await html that 等待检查等待的看看它是否有already完全的 如果 可等待已经完成 那么该方法将继续 运行 同步
  • std::vector 与 std::stack

    有什么区别std vector and std stack 显然 向量可以删除集合中的项目 尽管比列表慢得多 而堆栈被构建为仅后进先出的集合 然而 堆栈对于最终物品操作是否更快 它是链表还是动态重新分配的数组 我找不到关于堆栈的太多信息 但
  • 随着时间的推移,添加到 List 变得非常慢

    我正在解析一个大约有 1000 行的 html 表 我从一个字符串中添加 10 个字符串 td 每行到一个list td
  • 为什么 GCC 不允许我创建“内联静态 std::stringstream”?

    我将直接前往 MCVE include
  • 如何从本机 C(++) DLL 调用 .NET (C#) 代码?

    我有一个 C app exe 和一个 C my dll my dll NET 项目链接到本机 C DLL mynat dll 外部 C DLL 接口 并且从 C 调用 C DLL 可以正常工作 通过使用 DllImport mynat dl
  • 访问外部窗口句柄

    我当前正在处理的程序有问题 这是由于 vista Windows 7 中增强的安全性引起的 特别是 UIPI 它阻止完整性级别较低的窗口与较高完整性级别的窗口 对话 就我而言 我想告诉具有高完整性级别的窗口进入我们的应用程序 它在 XP 或
  • 方程“a + bx = c + dy”的积分解

    在等式中a bx c dy 所有变量都是整数 a b c and d是已知的 我如何找到整体解决方案x and y 如果我的想法是正确的 将会有无限多个解 由最小公倍数分隔b and d 但我只需要一个解决方案 我可以计算其余的 这是一个例
  • 人脸 API DetectAsync 错误

    我想创建一个简单的程序来使用 Microsoft Azure Face API 和 Visual Studio 2015 检测人脸 遵循 https social technet microsoft com wiki contents ar
  • ASP.NET Core 3.1登录后如何获取用户信息

    我试图在登录 ASP NET Core 3 1 后获取用户信息 如姓名 电子邮件 id 等信息 这是我在登录操作中的代码 var claims new List
  • C# 列表通用扩展方法与非通用扩展方法

    这是一个简单的问题 我希望 集合类中有通用和非通用方法 例如List
  • 使用 C# 中的 CsvHelper 将不同文化的 csv 解析为十进制

    C 中 CsvHelper 解析小数的问题 我创建了一个从 byte 而不是文件获取 csv 文件的类 并且它工作正常 public static List
  • 结构体的内存大小不同?

    为什么第一种情况不是12 测试环境 最新版本的 gcc 和 clang 64 位 Linux struct desc int parts int nr sizeof desc Output 16 struct desc int parts
  • LINQ:使用 INNER JOIN、Group 和 SUM

    我正在尝试使用 LINQ 执行以下 SQL 最接近的是执行交叉联接和总和计算 我知道必须有更好的方法来编写它 所以我向堆栈团队寻求帮助 SELECT T1 Column1 T1 Column2 SUM T3 Column1 AS Amoun
  • C# 动态/expando 对象的深度/嵌套/递归合并

    我需要在 C 中 合并 2 个动态对象 我在 stackexchange 上找到的所有内容仅涵盖非递归合并 但我正在寻找能够进行递归或深度合并的东西 非常类似于jQuery 的 extend obj1 obj2 http api jquer
  • 为什么使用小于 32 位的整数?

    我总是喜欢使用最小尺寸的变量 这样效果就很好 但是如果我使用短字节整数而不是整数 并且内存是 32 位字可寻址 这真的会给我带来好处吗 编译器是否会做一些事情来增强内存使用 对于局部变量 它可能没有多大意义 但是在具有数千甚至数百万项的结构
  • 如何在 Android 中使用 C# 生成的 RSA 公钥?

    我想在无法假定 HTTPS 可用的情况下确保 Android 应用程序和 C ASP NET 服务器之间的消息隐私 我想使用 RSA 来加密 Android 设备首次联系服务器时传输的对称密钥 RSA密钥对已在服务器上生成 私钥保存在服务器
  • 在 WPF 中使用 ReactiveUI 提供长时间运行命令反馈的正确方法

    我有一个 C WPF NET 4 5 应用程序 用户将用它来打开某些文件 然后 应用程序将经历很多动作 读取文件 通过许多插件和解析器传递它 这些文件可能相当大 gt 100MB 因此这可能需要一段时间 我想让用户了解 UI 中发生的情况
  • C++ 中的参考文献

    我偶尔会在 StackOverflow 上看到代码 询问一些涉及函数的重载歧义 例如 void foo int param 我的问题是 为什么会出现这种情况 或者更确切地说 你什么时候会有 对参考的参考 这与普通的旧参考有何不同 我从未在现
  • Mono 应用程序在非阻塞套接字发送时冻结

    我在 debian 9 上的 mono 下运行一个服务器应用程序 大约有 1000 2000 个客户端连接 并且应用程序经常冻结 CPU 使用率达到 100 我执行 kill QUIT pid 来获取线程堆栈转储 但它总是卡在这个位置
  • 使用 WGL 创建现代 OpenGL 上下文?

    我正在尝试使用 Windows 函数创建 OpenGL 上下文 现代版本 基本上代码就是 创建窗口类 注册班级 创建一个窗口 choose PIXELFORMATDESCRIPTOR并设置它 创建旧版 OpenGL 上下文 使上下文成为当前

随机推荐

  • 为 Python 2.4 改进“with”语句的直接替换

    您能否建议一种方法来编写可在 Python 2 4 中使用的 with 语句的直接替换代码 这将是一个 hack 但它可以让我更好地将我的项目移植到 Python 2 4 EDIT 删除了不相关的元类草图 只需使用 try finally
  • 获取调用表单的名称空间

    我想要一个宏this ns这样它就会返回调用它的位置的名称空间 例如 如果我有这段代码 ns nstest main require nstest core as nstest defn ns str x gt x getName name
  • 如何在 Python 中仅列出 zip 存档中的文件夹?

    如何仅列出 zip 存档中的文件夹 这将列出存档中的每个文件夹和文件 import zipfile file zipfile ZipFile samples sample zip r for name in file namelist pr
  • 在Python中计算结构体的CRC

    我有以下结构 来自 C 中的 NRPE 守护程序代码 typedef struct packet struct int16 t packet version int16 t packet type uint32 t crc32 value
  • Webview 中的 Java 空指针异常

    我试图搜索这个问题 但这个错误看起来与这个错误 https stackoverflow com questions 21866459 android nullpointerexception on webview 我的 google pla
  • 如何减少 JSF 中的 javax.faces.ViewState

    减少 JSF 中视图状态隐藏字段大小的最佳方法是什么 我注意到我的视图状态约为 40k 这会在每次请求和响应时下降到客户端并返回到服务器 特别是到达服务器时 这对用户来说会显着减慢 我的环境 JSF 1 2 MyFaces Tomcat T
  • x11 - 导入错误:没有名为“kivy.core.window.window_x11”的模块

    目前我正在尝试构建一个我通过 buildozer 用 Python 和 Kivy 编写的应用程序 无论我在做什么 我都会遇到 window x11 的问题 即使我在代码中注释掉所有与 Windows 相关的内容或执行本文中描述的所有操作 这
  • Visual Studio - 在哪里定义自定义路径宏?

    我刚刚打开了其他人的 Visual Studio 项目 在他们的构建属性中 他们有一些用于包含和 lib 目录的自定义路径宏 宏名称是这样的 MY WHATEVER INCLUDE DIR 我可以手动将每个宏替换为真实路径 但最好只使用宏
  • 将服务连接到现有的流星帐户

    我正在设置一个流星应用程序 其中涉及使用用户名和密码进行注册 然后希望将该帐户与 Facebook 和 Twitter 连接起来 我只需使用帐户包即可轻松启动并运行第一部分 但是当我有一个登录用户调用 Meteor loginWithFac
  • mysqli_connect(): (HY000/2002): 无法建立连接,因为目标机器主动拒绝

    我知道有很多这样的问题 但我没有找到任何解决方案 我尝试过的事情 检查防火墙 重新启动我的电脑和 Apache 服务器 重新启动MYSQL 检查了我的代码 尝试了我所知道的和在互联网上找到的一切 这是我的代码
  • 在 IIS 托管的 ASP.NET Web 应用程序中打开页面时显示“找不到资源”

    我正在使用 IIS 8 5 将 Web 应用程序 Net 4 5 托管到远程服务器 该应用程序在本地和远程 IIS 上运行良好 但是有一个页面 Reports ReportsMain aspx 导致错误 找不到资源 我确保该页面存在 我还确
  • 在 Java 中打开现有文件并关闭它。

    是否可以在java中打开一个文件附加数据并关闭多次 例如 psuedocode class variable declaration FileWriter writer1 new FileWriter filename fn1 writer
  • 在 Chrome 中检索浏览器语言

    我一直在尝试让 momentjs 正确检测浏览器语言并本地化时间显示 按照使用 Moment js 进行区域设置检测 https stackoverflow com questions 25725882 locale detection w
  • 如何在 Python 中执行相当于预处理器指令的操作?

    有没有办法在 Python 中执行以下预处理器指令 if DEBUG lt do some code gt else lt do some other code gt endif There s debug 这是编译器预处理的特殊值 if
  • 如何将mysql数据库移动到另一个安装点

    我有一个 MySQL 数据库 它变得越来越大 我想将整个数据库移动到另一个安装点 在那里我有足够的存储空间 我希望传输当前数据 并将新数据保存到新位置 软件堆栈 在 FreeBSD 6 上运行的 MySQL 5 当然其他答案也是有效的 但如
  • 如何支持滑动删除具有组合布局的 UICollectionView 列表中的行?

    以前对于表视图 这是在UITableViewDataSource委托回调tableView commit forRowAt 相关 API 中是否有等效功能新的集合视图 https developer apple com documentat
  • 格式化货币

    在下面的示例中 逗号是小数点分隔符 我有这个 125456 89 我想要这个 125 456 89 其他示例 23456789 89 gt 23 456 789 89 Thanks 看看这个例子 double value 12345 678
  • PyQt - 如何检查 QDialog 是否可见?

    我有个问题 我有这个代码 balls Ball for i in range 1 10 因此 当我说 Ball 时 这将在 QDialog 上绘制一个球 然后当这完成后 我正在移动球QDialog无限循环中 我想说类似的话while QDi
  • 具有维度的 Amazon Web Service CloudWatch 自定义指标

    我正在尝试将数据推送到 AWS CloudWatch 上的自定义指标 但想了解有关维度的更多信息以及如何使用它们 我已经阅读了 AWS 文档 但它并没有真正解释它们的用途以及它如何影响 AWS 管理控制台中的图形 UI 维度是进一步细分指标
  • 更改 IdentityServer4 实体框架表名称

    我正在尝试更改由 IdentityServer4 的 PersistedGrantDb 和 ConfigurationDb 创建的默认表名称 并让实体框架生成正确的 SQL 例如 而不是使用实体IdentityServer4 EntityF