简体   繁体   English

实体框架MVC和多个数据库

[英]Entity Framework MVC and multiple databases

I've put together an MVC application using a repository pattern with Entity Framework and everything is going smoothly - but I've run into a stopping block and I'm not sure how to proceed. 我已经将一个使用存储库模式的MVC应用程序与Entity Framework放在一起,并且一切都进行得很顺利-但是我遇到了一个障碍,我不确定如何继续。

I have a few dozen databases with the same schema, and I want to be able to choose one or many at runtime. 我有几十个具有相同架构的数据库,我希望能够在运行时选择一个或多个数据库。 For example, let's say I start with a database of users (not made yet). 例如,假设我从一个用户数据库(尚未建立)开始。 That user has connection string information associated with them (possibly more than one). 该用户具有与之关联的连接字符串信息(可能不止一个)。 Once the user has "logged in", I want the Enumerables I feed to my Views to contain matching data from all of the databases that user has access to. 用户“登录”后, 我希望输入到我的视图中的Enumerables包含用户有权访问的所有数据库中的匹配数据。

Here's an example of what I have right now: 这是我现在拥有的示例:

Entity: 实体:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations.Schema;

namespace Dashboard.Domain.Entities
{
    public class Flight
    {
        public Guid Id { get; set; }
        public string CarrierCode { get; set; }
        public string FlightNo { get; set; }
        public string MarketingCarrierCode { get; set; }
        public string MarketingFlightNo { get; set; }
        public string Type { get; set; }
        public string TailNo { get; set; }
        public string OriginIATA { get; set; }
        ...

    }
}

DB Context: 数据库上下文:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using Dashboard.Domain.Entities;

namespace Dashboard.Domain.Concrete
{
    public class EFDbContext : DbContext
    {
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Passenger>().ToTable("PAX");
        }
        public DbSet<Flight> Flights { get; set; }
        public DbSet<Passenger> PAX { get; set; }
        public DbSet<Airport> Airports { get; set; }
    }
}

Flight repository interface: 飞行库界面:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dashboard.Domain.Entities;

namespace Dashboard.Domain.Abstract
{
    public interface IFlightRepository
    {
        IQueryable<Flight> Flights { get; }
    }
}

EF Flight Repository: EF飞行资料库:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dashboard.Domain.Abstract;
using Dashboard.Domain.Entities;

namespace Dashboard.Domain.Concrete
{
    public class EFFlightRepository : IFlightRepository
    {
        private EFDbContext context = new EFDbContext();

        public IQueryable<Flight> Flights
        {
            get { return context.Flights; }
        }
    }
}

Controller: 控制器:

 public class FlightController : Controller
    {
        private IFlightRepository fRepository;
        private IPaxRepository pRepository;
        private IAirportRepository aRepository;
        public int PageSize = 10;

        public FlightController(IFlightRepository flightRepository, IPaxRepository paxRepository, IAirportRepository airportRepository)
        {
            this.fRepository = flightRepository;
            this.pRepository = paxRepository;
            this.aRepository = airportRepository;
        }

        public ViewResult List(byte status = 1, int page = 1)
        { ...

I want those repositories to contain all of the data from all of the connection strings specified, but I have no idea where to start. 我希望这些存储库包含指定的所有连接字符串中的所有数据,但是我不知道从哪里开始。 EF is getting my connection string from the web.config, but I need to be able to set it dynamically somehow and I need to put more than one database's data into the repository. EF从web.config获取我的连接字符串,但是我需要能够以某种方式动态设置它,而且我需要将多个数据库的数据放入存储库中。

Is this possible? 这可能吗? I should mention that the site is READ ONLY, so I won't need to write changes back to the DBs. 我应该提到该站点是只读的,因此我不需要将更改写回到数据库中。

UPDATE: 更新:

I've changed the code so I can pass a connection string to the constructor of my EF Repository, but when I try to merge the IQueryables from two different contexts, as below: 我更改了代码,以便可以将连接字符串传递给EF信息库的构造函数,但是当我尝试合并来自两个不同上下文的IQueryable时,如下所示:

public class EFFlightRepository : IFlightRepository
{
    private EFDbContext context1 = new EFDbContext(connectionstring1);
    private EFDbContext context2 = new EFDbContext(connectionstring2);
    private IQueryable<Flight> context;

    public EFFlightRepository()
    {
        context = (IQueryable<Flight>)context1.Flights.Union(context2.Flights);
    }
    public IQueryable<Flight> Flights
    {
        get { return context;}
    }
}

I get this exception: 我得到这个例外:

The specified LINQ expression contains references to queries that are associated with different contexts. 指定的LINQ表达式包含对与不同上下文关联的查询的引用。

How can I combine them so I can run my LINQ queries just like it's ONE set of data? 如何合并它们,以便像运行一组数据一样运行LINQ查询?

It is difficult to come up with a detailed solution because it really depends on your software design choices, but I think a possible solution consists of the following things: 很难给出详细的解决方案,因为它实际上取决于您的软件设计选择,但是我认为可能的解决方案包括以下几方面:

1) A method / class that creates a collection of DbContext objects using the DbContext constructor with connection string or connection string name (is the same constructor) as Willian Werlang mentioned: 1)一种方法/类,该方法/类使用DbContext构造函数创建DbContext对象的集合,该构造函数具有与Willian Werlang所述的连接字符串或连接字符串名称(是相同的构造函数):

new DbContext("DB1");

2) Your repositories should be able to accept the list of DbContext's rather than a single one. 2)您的存储库应该能够接受DbContext的列表,而不是单个列表。 It could eg be injected with the constructor of it. 例如,可以将其注入其构造函数。

3) The retrieval methods should iterate over the repositories and load (eager load when detaching) the relevant objects. 3)检索方法应遍历存储库并加载(分离时急于加载)相关对象。

4) The retrieved objects could be detached from their DbContext using the following code: 4)可以使用以下代码将检索到的对象与其DbContext分离:

dbContext.Entry(entity).State = EntityState.Detached;

This isn't required but might be a consideration since you would return a mix of different data sources. 这不是必需的,但可能是一个考虑因素,因为您将返回不同数据源的混合。

5) The retrieved/detached objects should be added to a returned List<> or you could yield return the results one by one with IEnumerable<> is return type. 5)检索/分离的对象应添加到返回的List <>中,否则您可以使用IEnumerable <>逐个返回的结果作为返回类型。

Returning an IQueryable isn't possible in this case but an IEnumerable will do as result. 在这种情况下,无法返回IQueryable,但将结果作为IEnumerable。

An example of a simple retrieval method for a flight repository could be something like: 飞行库的简单检索方法示例如下:

public IEnumerable<Flight> GetFlights() {
    // dbContexts is an IEnumerable<DbContext> that was injected in the constructor
    foreach (var ctx in dbContexts) {
        foreach (var flight in ctx.Flights) {
            yield return flight;
        }
    }
}

You can set multiples databases on your web.config, but with different names, so your DbContext's can receive the name of the database you want as parameter, like: 您可以在web.config上设置多个数据库,但使用不同的名称,因此DbContext可以接收所需的数据库名称作为参数,例如:

new DbContext("DB1");

This way you can choose from which database you'll get the data but I don't think you can get data from multiples bases at the same time with only onde dbContext; 这样,您可以选择从哪个数据库中获取数据,但是我认为仅使用onde dbContext不能同时从多个数据库中获取数据。

My solution was to change my Repository classes to take a connection string parameter, like this: 我的解决方案是将我的存储库类更改为采用连接字符串参数,如下所示:

namespace Dashboard.Domain.Concrete
{
    public class EFFlightRepository : IFlightRepository
    {
        private EFDbContext context;

        public IQueryable<Flight> Flights
        {
            get { return context.Flights;}
        }

        public EFFlightRepository(string connectionString)
        {
            context = new EFDbContext(connectionString);
        }
    }
}

Then create a factory class (using Ninject.Extensions.Factory) to pass the parameter when the repository is being created ( How to pass parameters down the dependency chain using Ninject ): 然后创建一个工厂类(使用Ninject.Extensions.Factory)在创建存储库时传递参数如何使用Ninject沿依赖关系链向下传递参数 ):

namespace Dashboard.Domain.Factories
{
    public interface IFlightRepoFactory
    {
        IFlightRepository CreateRepo(string connectionString);
    }
}

I have another Factory class that produces a list of Repositories based on a list of strings (connection strings to feed to the individual repository classes). 我还有另一个Factory类,它基于字符串列表(用于馈送到各个存储库类的连接字符串)生成一个存储库列表。

namespace Dashboard.Domain.Factories
{
    public interface IRepoCollectionFactory
    {
        IRepositoryCollection CreateCollection(List<string> connectionStrings);
    }
}

Then, in my controller class, I iterate through the Collection generated by the Collection Factory, running whatever query needs to be run on each set of repositories, and combine the results. 然后,在我的控制器类中,遍历Collection Factory生成的Collection,运行需要在每组存储库上运行的任何查询,然后合并结果。

This ultimately gives me a list that contains all of the data from each query on each repository. 最终,这给了我一个列表,其中包含每个存储库中每个查询的所有数据。

public FlightController(IRepoCollectionFactory repoCollectionFactory)
{
    this.repoCollectionFactory = repoCollectionFactory;
    this.collection = repoCollectionFactory.CreateCollection(new List<string> { 
                // each connection string for each database here
    });
}

Bindings in Ninject class: Ninject类中的绑定:

    private void AddBindings()
    {
        ninjectKernel.Bind<IFlightRepoFactory>().ToFactory();
        ninjectKernel.Bind<IAirportRepoFactory>().ToFactory();
        ninjectKernel.Bind<IPaxRepoFactory>().ToFactory();
        ninjectKernel.Bind<IRepoFactory>().ToFactory();
        ninjectKernel.Bind<IRepoCollectionFactory>().ToFactory();

        ninjectKernel.Bind<IRepositories>().To<EFRepositories>();
        ninjectKernel.Bind<IRepositoryCollection>().To<EFRepositoryCollection>();
        ninjectKernel.Bind<IFlightRepository>().To<EFFlightRepository>();
        ninjectKernel.Bind<IPaxRepository>().To<EFPaxRepository>();
        ninjectKernel.Bind<IAirportRepository>().To<EFAirportRepository>();
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM