如何使用 EF 和依赖注入在具有相同架构但不同名称的数据库之间进行更改?

How to change between databases with the same schema but different names using EF and dependency injection?

我有一个 Web API Web 服务,它使用 EF 进行数据库操作,使用 Unity 进行依赖项注入。我有多个名称不同但模式相同的数据库。每个零售店有一个数据库。当用户登录时,根据他的权限,他可以 select 他想与哪个商店合作。这是使用依赖项注入的挑战,因为我必须在注入存储库后更改数据库。我有一些有用的东西,但不确定这是否是最好的方法。

我的具体问题是:

我在尝试解决此问题时查看的一些问题是 this one, , and 。但是,其中 none 直接完成了我正在尝试做的事情。以下是我目前的解决方案。

这是我的存储库及其界面。为了简洁起见,我省略了一些方法:

IGenericRepository

public interface IGenericRepository<T> where T: class
{
    IQueryable<T> Get();
    void ChangeDatabase(string database);
    void Update(T entityToUpdate);
    void Save();
}

通用存储库

public class GenericRepository<TDbSet, TDbContext> : 
    IGenericRepository<TDbSet> where TDbSet : class
    where TDbContext : DbContext, new()
{
    internal DbContext Context;
    internal DbSet<TDbSet> DbSet;
    public GenericRepository() : this(new TDbContext())
    {
    }

    public GenericRepository(TDbContext context)
    {
        Context = context;
        DbSet = Context.Set<TDbSet>();
    }

    public virtual IQueryable<TDbSet> Get()
    {
        return DbSet;
    }       

    public void ChangeDatabase(string database)
    {
        var dbConnection = Context.Database.Connection;

        if (database == null || dbConnection.Database == database)
            return;

        if (dbConnection.State == ConnectionState.Closed)
        {
            dbConnection.Open();
        }

        Context.Database.Connection.ChangeDatabase(database);
    }

    public virtual void Update(TDbSet entityToUpdate)
    {
        DbSet.Attach(entityToUpdate);
        Context.Entry(entityToUpdate).State = EntityState.Modified;
    }

    public virtual void Save()
    {
        Context.SaveChanges();
    }
}

为了使用依赖项注入,我注入了一个存储库工厂,我可以向其传递数据库名称。工厂使用连接字符串的默认数据库创建一个存储库,将数据库更改为指定的数据库,然后 returns 存储库。

IRepositoryFactory

public interface IRepositoryFactory     
{
    IGenericRepository<TDbSet> GetRepository<TDbSet>(string dbName) where TDbSet : class;
}

StoreEntitiesFactory

public class StoreEntitiesFactory : IRepositoryFactory
{
    private bool _disposed;
    readonly StoreEntities _context;

    public StoreEntitiesFactory()
    {
        _context = new StoreEntities();
    }

    public IGenericRepository<TDbSet> GetRepository<TDbSet>(string dbName) where TDbSet : class
    {
        var repo = new GenericRepository<TDbSet, StoreEntities>(_context);

        repo.ChangeDatabase(dbName);

        return repo;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            _context.Dispose();
        }

        _disposed = true;
    }

    ~StoreEntitiesFactory()
    {
        Dispose(false);
    }
}

这就是我在 WebApiConfig 文件中注入存储库工厂的方式:

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        var container = new UnityContainer();       

        container.RegisterType<IRepositoryFactory, StoreEntitiesFactory>(new HierarchicalLifetimeManager());

        config.DependencyResolver = new UnityResolver(container);
    }
}

最后,这就是我在控制器中使用工厂的方式:

StoreController

public class StoreController : ApiController
{
    private readonly IRepositoryFactory _storeEntitiesRepoFactory;

    public StoreController(IRepositoryFactory storeEntitiesRepoFactory)
    {
        _storeEntitiesRepoFactory = storeEntitiesRepoFactory;        
    }

    [HttpGet]
    public IHttpActionResult Get()
    {
        var dbName = getStoreDbName(storeNumberWeGotFromSomewhere);

        try
        {
            var employeeRepo = _storeEntitiesRepoFactory.GetRepository<Employee>(dbName);
            var inventoryRepo = _storeEntitiesRepoFactory.GetRepository<Inventory>(dbName);

            var employees = employeeRepo.Get().ToList();
            var inventory = inventoryRepo.Get().ToList();
        }
        catch (Exception ex)
        {
            return InternalServerError();
        }
    }
}

我建议你使用一种称为策略模式的设计模式来解决这个问题。

此模式允许您在运行时在两个或多个策略之间切换。 参考:https://en.wikipedia.org/wiki/Strategy_pattern

对于注入,我建议您在 Unity 上注册两个具体的 类,每个数据库连接一个,并为需要传递字符串的那个调用 Resolve 方法以实例化数据库。

IUnityContainer container = new UnityContainer();
container.RegisterType<ICar, BMW>();
container.RegisterType<ICar, Audi>("LuxuryCar");

ICar bmw = container.Resolve<ICar>();  // returns the BMW object
ICar audi = container.Resolve<ICar>("LuxuryCar"); // returns the Audi object

参考:https://www.tutorialsteacher.com/ioc/register-and-resolve-in-unity-container

关于 Dispose,您可以将所有这些具体的 类 配置为单例数据库,并打开所有连接,但您需要验证这对您的应用程序是否可行。

我认为您可能希望您的 IRepositoryFactory 实现 return 相同的 dbName 相同的存储库。正如现在所写,使用两个不同的 dbName 参数调用 StoreEntitesFactory.GetRepository 会导致问题,因为它为每个存储库提供相同的 StoreEntites 实例。

为了说明...

public class DemonstrationController
{
    private readonly IRepositoryFactory _storeEntitiesRepoFactory;

    public DemonstrationController(IRepositoryFactory storeEntitiesRepoFactory)
    {
        _storeEntitiesRepoFactory = storeEntitiesRepoFactory;
    }

    [HttpGet]
    public IHttpActionResult Get()
    {
        var empRepo1 = _storeEntitiesRepoFactory.GetRepository("DB1");
        var empRepo2 = _storeEntitiesRepoFactory.GetRepository("DB2");

        // After the second line, empRepo1 is connected to "DB2" since both repositories are referencing the same
        // instance of StoreEntities
    }
}

如果您根据给定参数将 StoreEntitiesFactory 更改为 return 相同的存储库,这将解决该问题。

public class StoreEntitiesFactory : IRepositoryFactory
{
    private bool _disposed;
    private Dictionary<string, StoreEntities> _contextLookup;

    public StoreEntitiesFactory()
    {
        _contextLookup = new Dictionary<string, StoreEntities>();
    }

    public IGenericRepository<TDbSet> GetRepository<TDbSet>(string dbName) where TDbSet : class
    {
        if (!_contextLookup.TryGetValue(dbName, out StoreEntities context))
        {
            context = new StoreEntities();
            // You would set up the database here instead of in the Repository, and you could eliminate
            // the ChangeDatabase function.

            _contextLookup.Add(dbName, context);
        }
        return new GenericRepository<TDbSet, StoreEntities>(context);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                foreach (var context in _contextLookup.Values)
                {
                    context.Dispose();
                }
            }
            _disposed = true;
        }
    }
}

至于第二个问题,您需要工厂中的处理逻辑,因为它拥有正在创建的 StoreEntities 的实例。无需在其创建的存储库周围使用 using 语句,只需让 Unity 处理工厂即可。