我有一个Web API Web服务,该服务使用EF进行数据库操作,并使用Unity进行依赖项注入。我有多个名称不同但架构相同的数据库。每个零售商店只有一个数据库。用户登录时,可以根据自己的权限选择要使用的商店。这是使用依赖项注入的一个挑战,因为在注入存储库后,我必须更改数据库。我有一些有效的方法,但不确定是否是最佳方法。
我的具体问题是:
这是解决此问题的好方法吗?我看到过其他一些问题,它们涉及在运行时更改连接字符串,但是我想我要么必须在Web.Config
的每个商店中都有一个连接字符串,要么以某种方式动态地构建连接字符串。
我的工厂是否需要Dispose
逻辑?如果我直接注入存储库,我知道我将不需要它。由于我是从注入的工厂生成仓库,所以我可以信任Unity来处置仓库并在某个时候关闭数据库连接吗?我应该在生成的存储库周围使用using
语句吗?
我在尝试解决此问题时遇到的一些问题是this one,this one和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();
}
}
为了使用依赖注入,我要注入一个存储库工厂,我可以将数据库名称传递给该工厂。工厂使用连接字符串的默认数据库创建一个存储库,将该数据库更改为指定的数据库,然后返回该存储库。
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();
}
}
}
答案 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处理工厂即可。