实体框架MVC和多个数据库

本文关键字:数据库 框架 MVC 实体 | 更新日期: 2023-09-27 18:29:08

我已经使用实体框架的存储库模式构建了一个MVC应用程序,一切都很顺利,但我遇到了一个停止块,我不知道如何继续。

我有几十个具有相同模式的数据库,我希望能够在运行时选择一个或多个。例如,假设我从一个用户数据库(尚未制作)开始。该用户具有与其相关联的连接字符串信息(可能不止一个)。一旦用户"登录",我希望我提供给视图的枚举包含来自用户可以访问的所有数据库的匹配数据。

下面是我现在拥有的一个例子:

实体:

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上下文:

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; }
    }
}

飞行库接口:

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飞行库:

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; }
        }
    }
}

控制器:

 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)
        { ...

我希望这些存储库包含所有指定连接字符串中的所有数据,但我不知道从哪里开始。EF正在从web.config中获取我的连接字符串,但我需要能够以某种方式动态设置它,并且我需要将多个数据库的数据放入存储库中。

这可能吗?我应该提到的是,该网站是只读的,所以我不需要将更改写回数据库。

更新:

我已经更改了代码,以便可以将连接字符串传递给EF Repository的构造函数,但当我尝试合并来自两个不同上下文的IQueryables时,如下所示:

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;}
    }
}

我得到这个例外:

指定的LINQ表达式包含对以下查询的引用与不同的上下文相关联。

如何将它们组合起来,以便像运行一组数据一样运行LINQ查询?

实体框架MVC和多个数据库

很难找到详细的解决方案,因为这实际上取决于您的软件设计选择,但我认为可能的解决方案包括以下内容:

1)一个方法/类,使用具有连接字符串或连接字符串名称(与Willian Werlang提到的构造函数相同)的DbContext构造函数创建DbContext对象的集合:

new DbContext("DB1");

2)您的存储库应该能够接受DbContext的列表,而不是单个列表。例如,它可以注入它的构造函数。

3)检索方法应该在存储库上迭代并加载(分离时急切加载)相关对象。

4)可以使用以下代码将检索到的对象与其DbContext分离:

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

这不是必需的,但可能是一个考虑因素,因为您将返回不同数据源的混合。

5)检索/分离的对象应添加到返回的List<>或者您可以使用IEnumerable<>逐个返回结果是返回类型。

在这种情况下,返回IQueryable是不可能的,但结果是返回IEnumerable。

飞行储存库的一个简单检索方法示例可以是:

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;
        }
    }
}

您可以在web.config上设置多个数据库,但名称不同,因此您的DbContext可以接收您想要作为参数的数据库名称,如:

new DbContext("DB1");

通过这种方式,你可以选择从哪个数据库中获取数据,但我不认为你可以同时使用onde-dbContext从多个数据库获取数据;

我的解决方案是更改我的Repository类以采用连接字符串参数,如下所示:

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);
        }
    }
}

然后创建一个工厂类(使用Ninject.Extensions.factory)来在创建存储库时传递参数(如何使用Ninject将参数传递到依赖链):

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

我有另一个Factory类,它基于字符串列表(要馈送到各个存储库类的连接字符串)生成存储库列表。

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

然后,在我的控制器类中,我遍历Collection Factory生成的Collection,在每组存储库上运行需要运行的任何查询,并组合结果。

这最终给了我一个列表,其中包含每个存储库上每个查询的所有数据。

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

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>();
    }