【问题标题】:EF Core to call database based on parameter in APIEF Core 根据 API 中的参数调用数据库
【发布时间】:2021-07-30 09:31:33
【问题描述】:

我有一个在 .NET Core 中使用 EF Core 开发的 API。我必须为多个客户提供不同的数据(但相同的模式)。这是一个学校申请,由于竞争等原因,每所学校都希望单独保存他们的数据。所以我们为每所学校都有一个数据库。现在我的挑战是,基于一些参数,我想更改我的dbContext 对象的连接字符串。

例如,如果我调用 api/students/1 它应该得到所有来自学校 1 的学生,依此类推。我不确定在配置服务本身中是否有更好的方法来做到这一点。但我应该能够从我的客户端应用程序传递SchoolId

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<SchoolDataContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("APIConnectionString")));
    services.AddScoped<IUnitOfWorkLearn, UnitOfWorkLearn>();
}

2021 年 5 月 11 日


namespace LearnNew
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //Comenting to implement Mr Brownes Solution
            //services.AddDbContext<SchoolDataContext>(options =>
            //   options.UseSqlServer(
            //       Configuration.GetConnectionString("APIConnectionString")));



            services.AddScoped<IUnitOfWorkLearn, UnitOfWorkLearn>();


            services.AddControllers();


            services.AddHttpContextAccessor();

            services.AddDbContext<SchoolDataContext>((sp, options) =>
            {
                var requestContext = sp.GetRequiredService<HttpContext>();
                var constr = GetConnectionStringFromRequestContext(requestContext);
                options.UseSqlServer(constr, o => o.UseRelationalNulls());

            });

            ConfigureSharedKernelServices(services);

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "LearnNew", Version = "v1" });
            });
        }

        private string GetConnectionStringFromRequestContext(HttpContext requestContext)
        {
            //Trying to implement Mr Brownes Solution
            var host = requestContext.Request.Host;
            // Since I don't know how to get the connection string, I want to  
            //debug the host variable and see the possible way to get details of 
            //the host. Below line is temporary until the right method is identified
            return Configuration.GetConnectionString("APIConnectionString");
        }

        private void ConfigureSharedKernelServices(IServiceCollection services)
        {
            ServiceProvider serviceProvider = services.BuildServiceProvider();
            SchoolDataContext appDbContext = serviceProvider.GetService<SchoolDataContext>();

            services.RegisterSharedKernel(appDbContext);
        }

            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LearnNew v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }



    }
}

【问题讨论】:

标签: api entity-framework asp.net-core asp.net-web-api entity-framework-core


【解决方案1】:

您可以在配置 DbContext 时访问 HttpContext,如下所示:

        services.AddControllers();

        services.AddHttpContextAccessor();

        services.AddDbContext<SchoolDataContext>((sp, options) =>
        {
            var requestContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
            var constr = GetConnectionStringFromRequestContext(requestContext);
            options.UseSqlServer(constr, o => o.UseRelationalNulls());

        });

这段代码:

            var requestContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;                var constr = GetConnectionStringFromRequestContext(requestContext);
            options.UseSqlServer(constr, o => o.UseRelationalNulls());

将为每个请求运行,根据来自 HttpRequestContext 的详细信息配置连接字符串。

如果您需要在启动时使用您的 DbContext,请不要通过 DI 解决它。只需像这样配置一个连接:

        var ob  = new DbContextOptionsBuilder<SchoolDataContext>();
        var constr = "...";
        ob.UseSqlServer(constr);
        using (var db = new Db(ob.Options))
        {
            db.Database.EnsureCreated();
        }

但在生产环境中,您通常会提前创建所有租户数据库。

【讨论】:

  • 大卫您好,感谢您的回复。由于我是新手,如果我的问题太基本,请多多包涵。假设我有两所学校订阅了这个 API,第一所学校将尝试从 schoolapis.azurewebsites.net/api/students/1 访问学生的控制器,第二所学校从 schoolapis.azurewebsites.net/api/students/2 访问。由于我假设 DbContext 处于启动状态,.net 核心将在这两种情况下运行配置服务?由于任何原因,我不想在学校 1 中获得学校 2 的学生名单。
  • 这也意味着我必须将学校 ID 作为参数传递给所有控制器操作,对吗?有没有更好的办法?
  • 对学校 ID 使用 URL 参数可能不是最好的主意。相反,您通常使用用户的身份。身份验证数据、请求标头和 URL 都可以通过 HttpContext 获得。
  • 谢谢你,大卫。我将进行相同的测试并确认答案。再次感谢您的宝贵时间。我从 DbContextFactory 开始,但您的解决方案看起来更直接。
  • 嗨大卫,它给了我以下错误,你能帮帮我吗? System.InvalidOperationException:'没有为类型'Microsoft.AspNetCore.Http.HttpContext'注册服务。'我知道当你从你的水平看我时,我只是一个绝对的初学者,但我相信在你的帮助下我可以让它发挥作用。下面是完整的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-05
  • 2020-05-22
  • 1970-01-01
  • 1970-01-01
  • 2022-08-18
相关资源
最近更新 更多