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

时间:2019-06-07 16:54:38

标签: c# entity-framework asp.net-web-api dependency-injection asp.net-web-api2

我有一个Web API Web服务,该服务使用EF进行数据库操作,并使用Unity进行依赖项注入。我有多个名称不同但架构相同的数据库。每个零售商店只有一个数据库。用户登录时,可以根据自己的权限选择要使用的商店。这是使用依赖项注入的一个挑战,因为在注入存储库后,我必须更改数据库。我有一些有效的方法,但不确定是否是最佳方法。

我的具体问题是:

  • 这是解决此问题的好方法吗?我看到过其他一些问题,它们涉及在运行时更改连接字符串,但是我想我要么必须在Web.Config的每个商店中都有一个连接字符串,要么以某种方式动态地构建连接字符串。

  • 我的工厂是否需要Dispose逻辑?如果我直接注入存储库,我知道我将不需要它。由于我是从注入的工厂生成仓库,所以我可以信任Unity来处置仓库并在某个时候关闭数据库连接吗?我应该在生成的存储库周围使用using语句吗?

我在尝试解决此问题时遇到的一些问题是this onethis onethis one。但是,他们都没有直接完成我想做的事情。以下是我当前的解决方案。

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

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

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

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

2 个答案:

答案 0 :(得分:1)

我建议您使用一种称为“策略模式”的设计模式来解决此问题。

此模式允许您在运行时在两个或多个策略之间进行切换。 参考: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,您可以将所有这些具体类配置为Singletons到DB,并打开所有连接,但是您需要验证应用程序是否可行。

答案 1 :(得分:1)

我认为您可能希望您的IRepositoryFactory实现为相同的dbName返回相同的存储库。就像现在所写的那样,用两个不同的StoreEntitesFactory.GetRepository参数调用dbName会导致问题,因为它为每个存储库都提供了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以根据给定的参数返回相同的存储库,则可以解决该问题。

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处理工厂即可。