Entity framework 对多个数据库使用Breeze/EntityFramework/WebAPI

Entity framework 对多个数据库使用Breeze/EntityFramework/WebAPI,entity-framework,breeze,Entity Framework,Breeze,我们目前有一个使用DevForce 2012的Silverlight应用程序。像Silverlight世界的许多地方一样,我们已经开始移植到HTML5。我们将使用Breeze支持的Angular和EntityFramework/WebAPI 我们的每个客户都有自己的数据库,都共享同一型号。因为我们有几百个客户,我们的web.config包含几百个连接字符串。当用户登录时,他们输入其帐户代码,该代码直接链接到连接字符串。DevForce有一个“数据源扩展”的概念,这就是我们的Silverlight

我们目前有一个使用DevForce 2012的Silverlight应用程序。像Silverlight世界的许多地方一样,我们已经开始移植到HTML5。我们将使用Breeze支持的Angular和EntityFramework/WebAPI

我们的每个客户都有自己的数据库,都共享同一型号。因为我们有几百个客户,我们的web.config包含几百个连接字符串。当用户登录时,他们输入其帐户代码,该代码直接链接到连接字符串。DevForce有一个“数据源扩展”的概念,这就是我们的Silverlight应用程序用来获得正确连接的概念。因此,我们的配置示例如下

<connectionStrings>
   <add name="Entities_123" connectionString="myConnectionString" />    
   <add name="Entities_456" connectionString="myConnectionString2" />
   ...
</connectionStrings>

...
因此,用户在登录时输入“456”作为其帐户代码,我们将该值作为“数据源扩展”传递给DevForce,由于DevForce magic,该连接在会话的其余部分与用户关联


我最头疼的是如何用Breeze/EF做类似的事情。我浏览过网络,找不到任何关于如何使用Breeze连接多个数据库而不必创建多个控制器/上下文类的示例。我猜我需要以某种方式使用DBContextFactory,但我甚至不知道从哪里开始。

我认为这与数据库选择问题一样是一个安全问题。因此,我将继续您的做法,让服务器根据经过身份验证的用户确定数据库id

客户端不应直接知道或影响数据库id的选择。这是属于服务器的私事

    var adapter = breeze.config.getAdapterInstance('ajax');
    adapter.defaultSettings = {
        headers: { "account": account.user.accountNumber }
    };
因此,您不必在客户端进行任何更改。从客户端的角度来看,只有一个端点,每个端点都是相同的

服务器(Web API) 您不需要每个数据库都有一个控制器。您可能出于其他原因需要多个控制器,但这是由其他问题驱动的,而不是这个问题

在(可能是唯一的)WebAPI控制器中,您可以通过某种方式获得数据库id。我不知道你今天在Silverlight+DevForce中是怎么做到的;在您的Web API控制器中可能采用相同的方法

您的控制器将实例化一个
EFContextProvider
。。。或者更好的是,一个存储库/工作单元组件,它封装了一个
EFContextProvider
,并传递数据库id

您可能无法在控制器的构造函数中获取数据库id,因为此时请求对象不可用。在本例中,我们将在控制器的
Initialize
方法中告诉存储库

以下是可能适用于您的Web API控制器的开始:

[BreezeController]
public class YourController : ApiController {
    private readonly YourRepository _repository;

    // default ctor
    public YourController() : this(null) { }

    // Test / Dependency Injection ctor.
    // Todo: inject via an IYourRepository interface rather than "new" the concrete class
    public YourController(YourRepository repository) {
        _repository = repository ?? new YourRepository();
    }

    protected override void Initialize(HttpControllerContext controllerContext) {
        base.Initialize(controllerContext);
        _repository.SetDatabaseId(getDatabaseId());
    }

    /// <summary>
    /// Get the DatabaseId from ???
    /// </summary>
    private string getDatabaseId() {
        try {
            return ...; // your logic here. The 'Request' object is available now
        } catch  {
            return String.Empty;
        }
    }

    ...
}

很明显你得做点别的。。。任何适合实例化连接到与提供的数据库id匹配的数据库的
DbContext
的内容。这是您提到的
DBContextFactory
的位置。我假设您知道如何处理这个问题。

我认为这与数据库选择问题一样是一个安全问题。因此,我将继续您的做法,让服务器根据经过身份验证的用户确定数据库id

客户端不应直接知道或影响数据库id的选择。这是属于服务器的私事

    var adapter = breeze.config.getAdapterInstance('ajax');
    adapter.defaultSettings = {
        headers: { "account": account.user.accountNumber }
    };
因此,您不必在客户端进行任何更改。从客户端的角度来看,只有一个端点,每个端点都是相同的

服务器(Web API) 您不需要每个数据库都有一个控制器。您可能出于其他原因需要多个控制器,但这是由其他问题驱动的,而不是这个问题

在(可能是唯一的)WebAPI控制器中,您可以通过某种方式获得数据库id。我不知道你今天在Silverlight+DevForce中是怎么做到的;在您的Web API控制器中可能采用相同的方法

您的控制器将实例化一个
EFContextProvider
。。。或者更好的是,一个存储库/工作单元组件,它封装了一个
EFContextProvider
,并传递数据库id

您可能无法在控制器的构造函数中获取数据库id,因为此时请求对象不可用。在本例中,我们将在控制器的
Initialize
方法中告诉存储库

以下是可能适用于您的Web API控制器的开始:

[BreezeController]
public class YourController : ApiController {
    private readonly YourRepository _repository;

    // default ctor
    public YourController() : this(null) { }

    // Test / Dependency Injection ctor.
    // Todo: inject via an IYourRepository interface rather than "new" the concrete class
    public YourController(YourRepository repository) {
        _repository = repository ?? new YourRepository();
    }

    protected override void Initialize(HttpControllerContext controllerContext) {
        base.Initialize(controllerContext);
        _repository.SetDatabaseId(getDatabaseId());
    }

    /// <summary>
    /// Get the DatabaseId from ???
    /// </summary>
    private string getDatabaseId() {
        try {
            return ...; // your logic here. The 'Request' object is available now
        } catch  {
            return String.Empty;
        }
    }

    ...
}

很明显你得做点别的。。。任何适合实例化连接到与提供的数据库id匹配的数据库的
DbContext
的内容。这是您提到的
DBContextFactory
的位置。我假设您知道如何处理这个问题。

我认为这与数据库选择问题一样是一个安全问题。因此,我将继续您的做法,让服务器根据经过身份验证的用户确定数据库id

客户端不应直接知道或影响数据库id的选择。这是属于服务器的私事

    var adapter = breeze.config.getAdapterInstance('ajax');
    adapter.defaultSettings = {
        headers: { "account": account.user.accountNumber }
    };
因此,您不必在客户端进行任何更改。从客户端的角度来看,只有一个端点,每个端点都是相同的

服务器(Web API) 您不需要每个数据库都有一个控制器。您可能出于其他原因需要多个控制器,但这是由其他问题驱动的,而不是这个问题

在(可能是唯一的)WebAPI控制器中,您可以通过某种方式获得数据库id。我不知道你今天在Silverlight+DevForce中是怎么做到的;在您的Web API控制器中可能采用相同的方法

您的控制器将实例化一个
EFContextProvider
。。。或者,更好的是,一个封装代码的存储库/工作单元组件
//--------------------------------------------------------------------------    ----
// <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)
        {
        }
    }
}
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);
        }
    }
}
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;
        }
    }
}
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)
        //{
        //}
    }
}
    var adapter = breeze.config.getAdapterInstance('ajax');
    adapter.defaultSettings = {
        headers: { "account": account.user.accountNumber }
    };
[BreezeController]
public class MyController : ApiController
{
    private readonly MyRepository _repository = new MyRepository();

    protected override void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);

        IEnumerable<string> values;
        if (Request.Headers.TryGetValues("account", out values))
            _repository.SetAccountNumber(values.FirstOrDefault());
    }

    ...
}
public class MyRepository
{
    private MyContextProvider _contextProvider = new MyContextProvider("");

    private MyContext Context { get { return _contextProvider.Context; } }


    public void SetAccountNumber(string accountNumber)
    {
        _contextProvider = new MyContextProvider(accountNumber);
    }
}


public class MyContextProvider : EFContextProvider<MyContext>
{
    private readonly MyContextFactory _contextFactory;

    public MyContextProvider(string accountNumber)
    {
        _contextFactory = new MyContextFactory();
        _contextFactory.AccountNumber = accountNumber;
    }

    protected override MyContext CreateContext()
    {
        var context = _contextFactory.Create();
        return context;
    }
}


public class MyContextFactory : IDbContextFactory<MyContext>
{
    public string AccountNumber { get; set; }

    public MyContext Create()
    {
        var dbName = "MyEntities" + (string.IsNullOrEmpty(AccountNumber) ? "" : "_" + AccountNumber);
        var contextInfo = new DbContextInfo(typeof(MyContext), new DbConnectionInfo(dbName));
        var context = contextInfo.CreateInstance() as MyContext;

        return context;
    }
}