假设您的数据库中有这三个连接字符串,其中第一个连接字符串是 EF 在您创建模型时在设计时创建的。其余两个是您自己添加并希望在运行时使用的。
<connectionStrings>
<add name="TestDbContext" connectionString="metadata=res://*/Models.TestModel.csdl|res://*/Models.TestModel.ssdl|res://*/Models.TestModel.msl;provider=System.Data.SqlClient;provider connection string="data source=TEST_DB_SERVER\test_dev;initial catalog=Test_1;persist security info=True;user id=dbuser_1;password=pwd1;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
<add name="TestDbContext_1" connectionString="metadata=res://*/Models.TestModel.csdl|res://*/Models.TestModel.ssdl|res://*/Models.TestModel.msl;provider=System.Data.SqlClient;provider connection string="data source=TEST_DB_SERVER\test_dev;initial catalog=Test_1;persist security info=True;user id=dbuser_1;password=pwd1;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
<add name="TestDbContext_2" connectionString="metadata=res://*/Models.TestModel.csdl|res://*/Models.TestModel.ssdl|res://*/Models.TestModel.msl;provider=System.Data.SqlClient;provider connection string="data source=TEST_DB_SERVER\test_dev;initial catalog=Test_2;persist security info=True;user id=dbuser_2;password=pwd2;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
假设您有一个 Breeze WebAPI 控制器 TestController,它在内部使用实现 ITestRepo 接口的 Repository 类 TestRepo。如果不是这种情况,那么您将必须遵循此模式,因为 Unity 依赖注入 (DI) 需要它。顺便说一句,我不打算深入研究如何获得 Unity DI 包和类似的东西。所以假设你已经安装了 Unity DI,下面是 UnityResolver 类的完整实现
using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;
namespace Test.Common.DI
{
public class UnityResolver : IDependencyResolver
{
public IUnityContainer container;
public UnityResolver(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch (ResolutionFailedException)
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}
public void Dispose()
{
container.Dispose();
}
}
}
这是在 WebApiConfig.cs 文件中配置 Unity DI 的方式
using Test.Common.DI;
using Microsoft.Practices.Unity;
using QuickStaff.Controllers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace Test
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
string[] connNames = TestController.GetConnectionStringNamesCore();
if (connNames.Length <= 0)
{
throw new Exception("ERROR: There needs to be at least one connection string configured in the web.config file with a name starting with 'TestDbContext_'");
}
// Web API configuration and services
var container = new UnityContainer();
container.RegisterType<ITestRepo, TestRepo>(new HierarchicalLifetimeManager());
container.RegisterInstance(new TestRepo(connNames[0])); // THIS IS NEEDED IN OERDER TO TRIGGER THE "TestController" CONSTRUCTOR THAT HAS ONE STRING ARGUMENT RATHER THAN THE DEFAULT
config.DependencyResolver = new UnityResolver(container);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
这是使用数据库优先方法的 EF 生成模型和 DBContext 类
//-------------------------------------------------------------------------- ----
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace TestModels
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class TestDbContext : DbContext
{
public TestDbContext()
: base("name=TestDbContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<EMP_EDUCATION> EMP_EDUCATION { get; set; }
public virtual DbSet<EMP_POSITIONS> EMP_POSITIONS { get; set; }
public virtual DbSet<EMP_STATUS> EMP_STATUS { get; set; }
public virtual DbSet<EMP_TALENT_TYPES> EMP_TALENT_TYPES { get; set; }
public virtual DbSet<EMPLOYEES> EMPLOYEES { get; set; }
public virtual DbSet<LOCATION_TYPES> LOCATION_TYPES { get; set; }
public virtual DbSet<LOCATIONS> LOCATIONS { get; set; }
public virtual DbSet<POSITION_CATEGORIES> POSITION_CATEGORIES { get; set; }
public virtual DbSet<PosJobClass> PosJobClass { get; set; }
public virtual DbSet<PRJ_LOCATIONS> PRJ_LOCATIONS { get; set; }
public virtual DbSet<PRJ_POSITIONS> PRJ_POSITIONS { get; set; }
public virtual DbSet<PRJ_STATUS> PRJ_STATUS { get; set; }
public virtual DbSet<PROJECTS> PROJECTS { get; set; }
public virtual DbSet<REPORTS> REPORTS { get; set; }
}
}
现在我们需要实现一个与上面同名的部分类,以便引入另一个构造函数,该构造函数将接受一个字符串参数,其中包含我们的用户将在客户端选择的连接字符串。所以这是那段代码
namespace Test.Models
{
using Breeze.ContextProvider.EF6;
using System;
using System.Data.Entity;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
public partial class TestDbContext : DbContext
{
public TestDbContext(string connectionString)
: base(connectionString)
{
}
}
}
现在我们有一个带有构造函数的 DbContext 类,该构造函数将连接字符串作为其参数,但问题是我们如何调用第二个构造函数,因为我们无法直接调用它,因为我们使用的是负责调用 DbContext 的 Breeze 的 EFContextProvider。好消息是我们可以覆盖 EFContextProvider,这里是代码
namespace Test.Models
{
using Breeze.ContextProvider.EF6;
using System;
using System.Data.Entity;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
public class EFContextProviderEx<T> : EFContextProvider<T> where T : class, new()
{
private string _connectionString;
public EFContextProviderEx(string connectionString){
_connectionString = connectionString;
}
protected override T CreateContext()
{
return (T)Activator.CreateInstance(typeof(T), _connectionString);
}
}
}
好的,到目前为止一切顺利。我们现在需要使用我们介绍的上述构造函数。实现 ITestRepo 接口的 TestRepo 类是我们执行此操作的地方,这里是 Respository 类的代码以及用于完成的接口代码
using Breeze.ContextProvider.EF6;
using Test.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Test.Controllers
{
public interface ITestRepo
{
string Metadata();
SaveResult SaveChanges(JObject saveBundle);
IQueryable<POSITION_CATEGORIES> PositionCategories();
}
public class TestRepo : ITestRepo
{
//public readonly EFContextProvider<TestDbContext> _contextProvider = new EFContextProvider<TestDbContext>();
public readonly EFContextProvider<TestDbContext> _contextProvider;
public TestRepo(string connectionString)
{
_contextProvider = new EFContextProviderEx<TestDbContext>(connectionString);
}
public string Metadata()
{
return _contextProvider.Metadata();
}
public Breeze.ContextProvider.SaveResult SaveChanges(Newtonsoft.Json.Linq.JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
public IQueryable<POSITION_CATEGORIES> PositionCategories()
{
return _contextProvider.Context.POSITION_CATEGORIES;
}
}
}
现在最后一块是我们的 Breeze 控制器。我们需要能够以某种方式将连接字符串信息传递给我们的 Breeze 控制器。我们这样做的方式是结合两件事。 1) 通过提供一个构造函数,该构造函数通过接口接收我们的存储库类的实例,以及 2) 通过在我们的控制器上创建一个 HttpPost API 方法 (SetConnectionString(...)) 来设置所需的连接字符串,以便每当我们想要更改我们简单地调用此 API 的连接字符串,然后我们的控制器开始针对适当的数据库工作。
让我们看一下控制器的代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Breeze.ContextProvider;
using Breeze.ContextProvider.EF6;
using Breeze.WebApi2;
using Test.Models;
using System.Web.Http.Controllers;
using System.Web;
using Microsoft.Practices.Unity;
using System.Configuration;
using Test.Common.DI;
namespace Test.Controllers
{
[BreezeController]
public class TestController : ApiController
{
private const string TEST_DB_CNTXT_PREFIX = "testdbcontext_";
//public readonly EFContextProvider<TestDbContext> _contextProvider = new EFContextProvider<TestDbContext>();
private readonly ITestRepo _repo;
//public TestController()
//{
// UNCOMMENT THIS IN CASE YOU HAVE SOME COMPILE ERROR ASKING FOR THE DEFAULT CONSTRUCTOR
//}
public TestController(ITestRepo repository)
{
_repo = repository;
}
[HttpGet]
public string[] GetConnectionStringNames()
{
string[] connNames = GetConnectionStringNamesCore();
SetConnectionStringCore(connNames[0]); // select this as the default on the UI too
return connNames;
}
public static string[] GetConnectionStringNamesCore()
{
string[] connNames = new string[0];
List<string> temp = new List<string>();
for (int i = 0; i < ConfigurationManager.ConnectionStrings.Count; i++)
{
string cn = ConfigurationManager.ConnectionStrings[i].Name;
if (cn.ToLower().StartsWith(TEST_DB_CNTXT_PREFIX))
{
temp.Add(cn.Substring(TEST_DB_CNTXT_PREFIX.Length));
}
}
connNames = temp.ToArray();
return connNames;
}
[HttpPost]
public void SetConnectionString([FromUri] string connectionString)
{
connectionString = SetConnectionStringCore(connectionString);
}
private string SetConnectionStringCore(string connectionString)
{
connectionString = TEST_DB_CNTXT_PREFIX + connectionString;
if (!string.IsNullOrEmpty(connectionString))
{
// REGISTER A NEW INSTANCE OF THE REPO CLASS WITH THE NEW CONN. STRING SO THAT ANY SUBSEQUENT CALLS TO OUR CONTROLLER WILL USE THIS INSTANCE AND THUS WE WILL BE TALKING TO THAT DATABASE
UnityResolver r = (UnityResolver)(this.ControllerContext.Configuration.DependencyResolver);
r.container.RegisterInstance(new TestRepo(connectionString));
}
return connectionString;
}
[HttpGet]
public string Metadata()
{
return _repo.Metadata();
}
[HttpPost]
public SaveResult SaveChanges(Newtonsoft.Json.Linq.JObject saveBundle)
{
return _repo.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<POSITION_CATEGORIES> PositionCategories()
{
return _repo.PositionCategories().OrderBy(pc => pc.POS_CAT_CODE);
}
//// GET api/<controller>
//public IEnumerable<string> Get()
//{
// return new string[] { "value1", "value2" };
//}
//// GET api/<controller>/5
//public string Get(int id)
//{
// return "value";
//}
//// POST api/<controller>
//public void Post([FromBody]string value)
//{
//}
//// PUT api/<controller>/5
//public void Put(int id, [FromBody]string value)
//{
//}
//// DELETE api/<controller>/5
//public void Delete(int id)
//{
//}
}
}
正如您在上面的代码中看到的,魔法发生在由 SetConnectionString(...) 调用的 SetConnectionStringCore(...) 内部。基本上我们所做的是在 UnityResolver 的帮助下告诉 WebAPI 框架将哪个 TestRepo 类的实例注入到我们的 WebAPI 控制器中。
如果您对其余代码徘徊,那么正在发生的事情是客户端(在我的情况下是 Angular SPA)预计将对我们控制器上的 GetConnectionStringNames() 方法进行 http 调用以获取所有可用的连接字符串并将其呈现给用户,以便他选择一个。一旦他选择了一个连接字符串,客户端就会调用控制器上的 SetConnectionString(…) 方法将其传送给 WebAPI,然后客户端进行的任何调用都将针对该数据库执行。另请注意,由于存在一些解析代码,我选择将连接字符串的一部分呈现给客户端。但是你可以有自己的逻辑。需要记住的一点是,在 WebApiConfig.cs 文件中,我们最初使用的是遇到的第一个连接字符串。
我希望这对其他人有所帮助,因为我真的很难让它发挥作用。但我仍然要感谢那些通过他们的帖子帮助我的人。这是我咨询过的页面列表。
http://www.asp.net/web-api/overview/advanced/dependency-injection
https://myadventuresincoding.wordpress.com/2013/03/27/c-using-unity-for-dependency-injection-in-an-asp-net-mvc-4-web-api/
Using a dynamic connection string with the Breeze EFContextProvider
Using Breeze/EntityFramework/WebAPI with multiple databases
http://cosairus.com/Blog/2015/3/10/programmatic-connection-strings-in-entity-framework-6
http://blogs.msdn.com/b/jmstall/archive/2012/05/11/per-controller-configuration-in-webapi.aspx
如您所见,我不需要重写 WebAPI 控制器的 Initialize 方法
protected override void Initialize(HttpControllerContext controllerContext)
您还可以在以下位置找到此解决方案
https://sskasim.wordpress.com/
更新:上面有问题。它不适用于多用户场景,因为我们正在更改 Web API 控制器的连接字符串,而不是控制器的实例。因此,您必须使用 ASP.NET Session 并使用适当的 connectionString 存储 _repo 的实例。