我想在运行时在 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" };
}
关于c# - 使用 MVC WebApi 2 在运行时更改数据库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42464645/