Skip to main content
 首页 » 编程设计

c#之使用 MVC WebApi 2 在运行时更改数据库

2024年02月27日3398°冷暖

我想在运行时在 REST Api 中更改与数据库的连接。我想放置一个请求变量,让 Api 决定使用哪个连接字符串。

例如: 我将值为“develop”的变量“dbid”放在请求 header 中,并将其发送到 Api。

Api 查看 header 并从 web.config 获取正确的连接字符串。

我有三层(数据、业务、api)。数据包含用于获取和设置数据的 EntityFramework。像这样:

public class WebsiteContext : IocDbContext, IWebsites 
{ 
    public DbSet<Website> Websites { get; set; } 
 
    public IEnumerable<Website> GetAll() 
    { 
        return Websites.ToList(); 
    } 
} 

(IoCDbContext.cs)

public class IocDbContext : DbContext, IDbContext 
{ 
    public IocDbContext() : base("develop") 
    { 
    } 
 
    public void ChangeDatabase(string connectionString) 
    { 
        Database.Connection.ConnectionString= connectionString; 
    } 
} 

在业务中,我有一个类可以从数据层检索数据并执行一些逻辑操作(这里不需要,但对故事来说仍然有好处)。

public class Websites : IWebsites 
{ 
    private readonly Data.Interfaces.IWebsites _websiteContext; 
 
    #region Constructor 
 
    public Websites(Data.Interfaces.IWebsites websiteContext) 
    { 
        _websiteContext = websiteContext; 
    } 
 
    #endregion 
 
    #region IWebsites implementation 
 
 
    public IEnumerable<Website> GetWebsites() 
    { 
        List<Data.Objects.Website> websiteDtos = _websiteContext.GetAll().ToList(); 
 
        return websiteDtos.Select(web => web.ToModel()).ToList(); 
    } 
 
    #endregion 
} 
 
public static class WebsiteMapper 
{ 
    public static Website ToModel(this Data.Objects.Website value) 
    { 
        if (value == null) 
            return null; 
 
        return new Website 
        { 
            Id = value.Id, 
            Name = value.Name 
        }; 
    } 
} 

最后但同样重要的是, Controller :

public class WebsiteController : ApiController 
{ 
    private readonly IWebsites _websites; 
 
    public WebsiteController(IWebsites websites) 
    { 
        _websites = websites; 
    } 
 
    public IEnumerable<Website> GetAll() 
    { 
        return _websites.GetWebsites().ToList(); 
    } 
} 

我的 Unity 配置:

public static void RegisterComponents() 
    { 
        var container = new UnityContainer(); 
 
        container.RegisterType<Business.Interfaces.IWebsites, Websites>(); 
 
        container.RegisterType<IDbContext, IocDbContext>(); 
        container.RegisterType<IWebsites, WebsiteContext>(); 
 
        // e.g. container.RegisterType<ITestService, TestService>(); 
 
        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container); 
        DependencyResolver.SetResolver(new UnityDependencyResolver(container)); 
    } 

如您所见,默认情况下使用名称为“develop”的连接字符串。这将返回一个名为“网站”的网站。现在我将标题变量“dbid”更改为“live”。 api 应该看到这个并且应该得到与名称“live”相对应的连接字符串。最后一部分是我正在尝试的,但没有任何效果。

这个我试过了:

  • 将 session 添加到 webapi。这意味着我打破了 REST api 的无状态思想:未完成
  • 静态也行不通,因为每个人都可以获得相同的连接字符串,但它的用户特定
  • 谷歌,但大多数示例对我不起作用
  • 正在搜索 StackOverflow...请参阅上一点。

这让我发疯!应该有一种方法可以更改由请求 header 中的值给出的连接字符串,对吧?

请您参考如下方法:

我在创建的 Multi-Tenancy 应用程序中有相同的场景,我为每个租户使用不同的连接字符串。

您选择的实现并不重要,但您必须确定如何区分每个连接字符串的每个请求。在我的应用程序中,我创建了一个自定义路由值,并在 url 中使用它来区分每个请求。重要的是创建这种机制,它必须是您在 DI 框架中根据每个请求注册的第一件事。

例如(使用 Ninject):

private static void RegisterServicdes(IKernel kernel) 
{ 
    kernel.Bind<ISiteContext>().To<SiteContext>().InRequestScope(); 
    kernel.Bind<IDbContextFactory>().To<DbContextFactory>().InRequestScope(); 
    // register other services... 
} 

而不是你的 DbContext 的实现,我会改成这样,然后总是通过 DbContextFactory 创建你的 DbContext 实例。

public class IocDbContext : DbContext, IDbContext 
{ 
    public IocDbContext(string connectionStringType) : base(connectionStringType) { } 
} 

然后你需要创建一个你在创建你的DbContext时使用的DbContextFactory,并将上面的类作为依赖。或者您可以将依赖项带入您的服务,并将其传递到 DbContextFactory。

public interface IDbContextFactory 
{ 
    TestModel CreateContext(); 
} 
 
public class DbContextFactory : IDbContextFactory 
{ 
    private string _siteType; 
    public DbContextFactory(ISiteContext siteContext) 
    { 
        _siteType = siteContext.Tenant; 
    } 
 
    public TestModel CreateContext() 
    { 
        return new TestModel(FormatConnectionStringBySiteType(_siteType)); 
    } 
 
    // or you can use this if you pass the IMultiTenantHelper dependency into your service 
    public static TestModel CreateContext(string siteName) 
    { 
        return new TestModel(FormatConnectionStringBySiteType(siteName)); 
    } 
 
    private static string FormatConnectionStringBySiteType(string siteType) 
    { 
        // format from web.config 
        string newConnectionString = @"data source={0};initial catalog={1};integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"; 
 
        if (siteType.Equals("a")) 
        { 
            return String.Format(newConnectionString, @"(LocalDb)\MSSQLLocalDB", "DbOne"); 
        } 
        else 
        { 
            return String.Format(newConnectionString, @"(LocalDb)\MSSQLLocalDB", "DbTwo"); 
        } 
    } 
} 

然后您可以在访问 DbContext 时像这样使用它:

public class DbAccess 
{ 
    private IDbContextFactory _dbContextFactory; 
    public DbAccess(IDbContextFactory dbContextFactory) 
    { 
        _dbContextFactory = dbContextFactory; 
    } 
 
    public void DoWork() 
    { 
        using (IocDbContext db = _dbContextFactory.CreateContext()) 
        { 
            // use EF here... 
        } 
    }    
} 

ISiteContext 接口(interface)实现(使用路由)。

public interface ISiteContext 
{ 
    string Tenant { get; } 
} 
 
public class SiteContext : ISiteContext 
{ 
    private const string _routeId = "tenantId"; 
 
    private string _tenant; 
    public string Tenant {  get { return _tenant; } } 
 
    public SiteContext() 
    { 
        _tenant = GetTenantViaRoute(); 
    } 
 
    private string GetTenantViaRoute() 
    { 
        var routedata = HttpContext.Current.Request.RequestContext.RouteData; 
 
        // Default Routing 
        if (routedata.Values[_routeId] != null) 
        { 
            return routedata.Values[_routeId].ToString().ToLower(); 
        } 
 
        // Attribute Routing 
        if (routedata.Values.ContainsKey("MS_SubRoutes")) 
        { 
            var msSubRoutes = routedata.Values["MS_SubRoutes"] as IEnumerable<IHttpRouteData>; 
            if (msSubRoutes != null && msSubRoutes.Any()) 
            { 
                var subRoute = msSubRoutes.FirstOrDefault(); 
                if (subRoute != null && subRoute.Values.ContainsKey(_routeId)) 
                { 
                    return (string)subRoute.Values 
                        .Where(x => x.Key.Equals(_routeId)) 
                        .Select(x => x.Value) 
                        .Single(); 
                } 
            } 
        } 
 
        return string.Empty; 
    } 
} 

API 操作:

[Route("api/{tenantId}/Values/Get")] 
[HttpGet] 
public IEnumerable<string> Get() 
{ 
 
    _testService.DoDatabaseWork(); 
 
    return new string[] { "value1", "value2" }; 
}