如何使用 EF 和依赖注入在具有相同架构但不同名称的数据库之间进行更改?
How to change between databases with the same schema but different names using EF and dependency injection?
我有一个 Web API Web 服务,它使用 EF 进行数据库操作,使用 Unity 进行依赖项注入。我有多个名称不同但模式相同的数据库。每个零售店有一个数据库。当用户登录时,根据他的权限,他可以 select 他想与哪个商店合作。这是使用依赖项注入的挑战,因为我必须在注入存储库后更改数据库。我有一些有用的东西,但不确定这是否是最好的方法。
我的具体问题是:
这是解决这个问题的好方法吗?我已经看到其他提到在 运行 时间更改连接字符串的问题,但我认为我要么必须在我的 Web.Config
中为每个商店设置一个连接字符串,要么以某种方式动态构建连接字符串。
我的工厂需要 Dispose
逻辑吗?如果我直接注入存储库,我知道我不需要它。由于我是从注入的工厂生成回购协议,我可以相信 Unity 会在某个时候处理回购协议并关闭数据库连接吗?我应该在生成的存储库周围使用 using
语句吗?
我在尝试解决此问题时查看的一些问题是 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 处理工厂即可。
我有一个 Web API Web 服务,它使用 EF 进行数据库操作,使用 Unity 进行依赖项注入。我有多个名称不同但模式相同的数据库。每个零售店有一个数据库。当用户登录时,根据他的权限,他可以 select 他想与哪个商店合作。这是使用依赖项注入的挑战,因为我必须在注入存储库后更改数据库。我有一些有用的东西,但不确定这是否是最好的方法。
我的具体问题是:
这是解决这个问题的好方法吗?我已经看到其他提到在 运行 时间更改连接字符串的问题,但我认为我要么必须在我的
Web.Config
中为每个商店设置一个连接字符串,要么以某种方式动态构建连接字符串。我的工厂需要
Dispose
逻辑吗?如果我直接注入存储库,我知道我不需要它。由于我是从注入的工厂生成回购协议,我可以相信 Unity 会在某个时候处理回购协议并关闭数据库连接吗?我应该在生成的存储库周围使用using
语句吗?
我在尝试解决此问题时查看的一些问题是 this 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();
}
}
为了使用依赖项注入,我注入了一个存储库工厂,我可以向其传递数据库名称。工厂使用连接字符串的默认数据库创建一个存储库,将数据库更改为指定的数据库,然后 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 处理工厂即可。