【问题标题】:Dynamic connection string to Web Api到 Web Api 的动态连接字符串
【发布时间】:2015-08-04 21:55:59
【问题描述】:

我正在通过 web api 公开我的存储库操作。存储库已使用实体框架和工作单元模式实现。我有同一个数据库的许多实例。每一个代表不同客户的数据。现在的问题是如何通过每个 webapi 调用动态设置连接字符串?我应该在每次调用时获取连接字符串参数吗?或者我应该为每个客户端托管 Web Api?

【问题讨论】:

  • DbContext 对象的预期生命周期是多少?我希望它是每个请求的新实例,但不想假设。
  • 是的,每个请求都有一个新实例,因为请求将随机来自不同的客户端
  • 您找到解决方案了吗?我也有完全相同的要求,所以需要你的帮助。

标签: asp.net-web-api entity-framework-6 connection-string


【解决方案1】:

根据提供的信息,我将使用相同的控制器并查找连接字符串,而不是为每个客户端托管单独的 Web API 实例。托管多个实例会更加复杂,并且所指出的唯一区别是连接字符串,我认为这种复杂性是不合理的。

我们需要做的第一件事是确定哪个客户端正在调用以获得适当的连接字符串。这可以通过令牌、标头、请求数据或路由来完成。路由是最简单的,也是客户端最容易访问的,所以我将演示如何使用它;但是,在决定如何做出决定时,请仔细考虑您的要求。

[Route( "{clientId}" )]
public Foo Get( string clientId ) { /* ... */ }

接下来我们需要为客户端获取正确的DbContext。我们想继续使用 DI,但这很复杂,因为直到创建 Controller 之后我们才知道构造对象需要什么连接字符串。因此,我们需要注入某种形式的工厂而不是对象本身。在这种情况下,我们将其表示为Func<string, IUnitOfWork>,并理解它将“clientId”作为字符串并返回适当实例化的IUnitOfWork。我们也可以为此使用命名接口。

[RoutePrefix("foo")]
public class FooController : ApiController
{  
    private Func<string, IUnitOfWork> unitOfWorkFactory;

    public FooController( Func<string, IUnitOfWork> unitOfWorkFactory )
    {
        this.unitOfWorkFactory = unitOfWorkFactory;
    }

    [Route( "{clientId}" )]
    public Foo Get( string clientId )
    {
        var unitOfWork = unitOfWorkFactory(clientId);
        // ...
    }
}

剩下的就是配置我们的依赖注入容器来为我们提供Func&lt;string, IUnitOfWork&gt;。这在实施之间可能会有很大差异。以下是在 Autofac 中执行此操作的一种可能方法。

protected override void Load( ContainerBuilder builder )
{
    // It is expected `MyDbContext` has a constructor that takes the connection string as a parameter
    // This registration may need to be tweaked depending on what other constructors you have.
    builder.Register<MyDbContext>().ForType<DbContext>().InstancePerRequest();

    // It is expected `UnitOfWork`'s constructor takes a `DbContext` as a parameter
    builder.RegisterType<UnitOfWork>().ForType<IUnitOfWork>().InstancePerRequest();

    builder.Register<Func<string, Bar>>(
        c =>
            {
                var dbContextFactory = c.Resolve<Func<string, DbContext>>();
                var unitOfWorkFactory = c.Resolve<Func<DbContext, IUnitOfWork>>();

                return clientId =>
                    {
                        // You may have injected another type to help with this
                        var connectionString = GetConnectionStringForClient(clientId);
                        return unitOfWorkFactory(dbContextFactory(connectionString));
                    };
            });
 }

使用 Autofac 是因为 cmets 指示当前正在使用 Autofac,尽管使用其他容器可能会产生类似的结果。

这样,控制器应该能够被实例化,并且每个请求都将使用适当的连接字符串。


基于链接项目的示例注册:

builder.Register<Func<string, IEmployeeService>>(
    c =>
        {
            var dbContextFactory = c.Resolve<Func<string, IMainContext>>();
            var unitOfWorkFactory = c.Resolve<Func<IMainContext, IUnitOfWork>>();
            var repositoryFactory = c.Resolve<Func<IMainContext, IEmployeeRepository>>();
            var serviceFactory = c.Resolve<Func<IUnitOfWork, IEmployeeService>>();

            return clientId =>
                {
                    // You may have injected another type to help with this
                    var connectionString = GetConnectionStringForClient(clientId);

                    IMainContext dbContext = dbContextFactory(connectionString);
                    IUnitOfWork unitOfWork = unitOfWorkFactory(dbContext);
                    IEmployeeRepository employeeRepository = repositoryFactory(dbContext);
                    unitOfWork.employeeRepositoty = employeeRepository;

                    return serviceFactory(unitOfWork);
                };
        });

如果您发现注册变得过于繁琐,因为需要手动进行一些接线,您可能需要在确定客户端后查看更新(或创建新)容器,以便您可以更多地依赖容器.

【讨论】:

  • 非常感谢,这是我创建的示例项目的链接,用于演示我正在使用的架构dropbox.com/sh/f2leb6i8kmggef2/AAC8rGfkeD73e19HX9Vf_qZYa?dl=0
  • @InTheWorldOfCodingApplications 看起来上面的内容适用于那里。但是,您使用的是 MVC 而不是 WebAPI,如问题所示(并且此答案显示),但如果您决定使用该路由,您仍然应该能够使用属性路由。您似乎还没有决定如何确定客户。
  • 您在数据库和控制器(服务)之间多了一个类,可以在注册时以与上面所示完全相同的方式进行处理。
  • 实际项目是一个web Api项目。这只是展示 Repository 和 Unit of Work 如何与 ServiceFacade 层交互的示例
  • 所以我们将有一个服务工厂而不是 UnitOfWork 工厂?上下文被注入到 Repository 和 UnitOfWork 如何通过删除 DI 来处理?
【解决方案2】:

您可以更改每个 DbContext 实例的连接字符串

例子:

public class AwesomeContext : DbContext
{
    public AwesomeContext (string connectionString)
        : base(connectionString)
    {
    }
    public DbSet<AwesomePeople> AwesomePeoples { get; set; }
}

然后像这样使用你的 DbContext:

   using(AwesomeContext context = new AwesomeContext("newConnectionString"))
   {
     return context.AwesomePeoples.ToList();
   }

根据有多少个 ConnectionStrings,您可以为客户端/字符串映射创建数据库表或将其保存在解决方案中(例如数组)。

如果您不能/不想更改构造函数,您也可以稍后再做

将此添加到您的 DbContext 覆盖:

    public void SetConnectionString(string connectionString)
    {
        this.Database.Connection.ConnectionString = connectionString;
    }

并在执行任何数据库操作之前调用该方法:

   using(AwesomeContext context = new AwesomeContext())
   {

    context.SetConnectionString(ConfigurationManager.ConnectionStrings["newConnectionString"].ConnectionString)
    return context.AwesomePeoples.ToList();

   }

【讨论】:

  • 我正在通过接口和 autofac 注入我的上下文。这就像 Public Class AwesomeContext: DbContext, IAwesomeContext
  • 我更新了答案,因此即使您正在注入上下文,您也可以更改连接字符串
  • 我注入的是 IAwesomeContext 而不是 DbContext
  • 我无法查看您的代码(您需要提供示例),但您可以将我的示例更改为您的实现。您需要做的就是将 SetConnectionString 方法添加到接口并在您的特定上下文类中实现它。然后在注入的变量上调用方法
  • 代码示例在这里 stackoverflow.com/q/30404108/1711287 。我已经进一步阐述了它
猜你喜欢
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 2011-12-11
  • 1970-01-01
  • 2016-06-22
  • 2018-09-20
  • 2014-02-05
  • 2014-08-08
相关资源
最近更新 更多