拥有一个SharePoint"远程网络"将管理多个租户数据库(最终是多个租户数据库连接)的数据的应用程序。实质上,每个操作将处理2个数据库。
第一个是我们的租赁数据库,我们存储特定于每个租户的信息。这可以是SharePoint OAuth客户端ID和机密,以及有关如何连接到租户的特定数据库(第二个数据库)的信息。这意味着在连接到第二个数据库之前,需要连接到第一个数据库。
我相信我知道如何使用Simple Injector进行HTTP请求。我可以在每个Web请求生存期内注册第一个连接类型(无论是使用ADO.NET的IDbConnection包装还是来自实体框架的TenancyDbContext)。
然后我可以注册一个抽象工厂来解析与租户特定数据库的连接。该工厂将依赖于第一个数据库类型以及Simple Injector Container。查询&需要访问租户数据库的命令将依赖于此抽象工厂,并通过将参数传递给工厂方法来使用它来获取与租户数据库的连接。
我的问题主要与如何在可能有或没有非空HttpContext.Current
的操作的上下文中处理这个问题有关。安装SharePoint应用程序时,我们有时会运行WCF .svc
服务来执行某些操作。当SharePoint调用此方法时,有时HttpContext
为空。我需要一个适用于两种情况的解决方案,对于两个数据库连接,这将确保在不再需要连接时处理这些连接。
我有一些使用LifetimeScope的旧示例代码,但我现在看到在nuget上有一个可用于Simple Injector的执行上下文范围包。我想知道是否应该使用它来为这两个数据库连接创建混合范围(有/没有HTTP上下文),如果是这样,它与使用Container.GetCurrentLifetimeScope
和Container.BeginLifetmeScope
的生命范围界定有什么不同? / p>
更新
我阅读了执行范围的生活方式,最后得到了以下3路混合:
var hybridDataAccessLifestyle = Lifestyle.CreateHybrid( // create a hybrid lifestyle
lifestyleSelector: () => HttpContext.Current != null, // when the object is needed by a web request
trueLifestyle: new WebRequestLifestyle(), // create one instance for all code invoked by the web request
falseLifestyle: Lifestyle.CreateHybrid( // otherwise, create another hybrid lifestyle
lifestyleSelector: () => OperationContext.Current != null, // when the object is needed by a WCF op,
trueLifestyle: new WcfOperationLifestyle(), // create one instance for all code invoked by the op
falseLifestyle: new ExecutionContextScopeLifestyle()) // in all other cases, create per execution scope
);
然而,我的问题实际上与如何创建一个依赖关系有关,该依赖关系将在root已经组合之后的某个时间获得它的连接字符串。下面是我提出的一些伪代码,它实现了我对如何实现它的想法:
public class DatabaseConnectionContainerImpl : IDatabaseConnectionContainer, IDisposable
{
private readonly AllTenantsDbContext _allTenantsDbContext;
private TenantSpecificDbContext _tenantSpecificDbContext;
private Uri _tenantUri = null;
public DatabaseConnectionContainerImpl(AllTenantsDbContext allTenantsDbContext)
{
_allTenantsDbContext = allTenantsDbContext;
}
public TenantSpecificDbContext GetInstance(Uri tenantUri)
{
if (tenantUri == null) throw new ArgumentNullException(“tenantUri”);
if (_tenantUri != null && _tenantUri.Authority != tenantUri.Authority)
throw new InvalidOperationException(
"You can only connect to one tenant database within this scope.");
if (_tenantSpecificDbContext == null) {
var tenancy = allTenantsDbContext.Set<Tenancy>()
.SingleOrDefault(x => x.Authority == tenantUri.Authority);
if (tenancy == null)
throw new InvalidOperationException(string.Format(
"Tenant with URI Authority {0} does not exist.", tenantUri.Authority));
_tenantSpecificDbContext = new TenantSpecificDbContext(tenancy.ConnectionString);
_tenantUri = tenantUri;
}
return _tenantSpecificDbContext
}
void IDisposable.Dispose()
{
if (_tenantSpecificDbContext != null) _tenantSpecificDbContext.Dispose();
}
}
底线是有一个运行时Uri
变量,用于确定连接字符串到TenantSpecificDbContext
实例的内容。此Uri变量将传递到所有WCF操作和HTTP Web请求中。由于在编写根之后运行时才知道这个变量,我不认为有任何方法可以将它注入到构造函数中。
任何比上述更好的想法,或者上面的想法是否有问题?
答案 0 :(得分:2)
由于您希望在同一AppDomain中的两个不同的上下文中运行操作(一个具有Web请求的可用性,何时没有),您需要使用hybrid lifestyle。混合生活方式从一种生活方式自动切换到另一种生活方式。 Simple Injector文档中给出的示例如下:
ScopedLifestyle scopedLifestyle = Lifestyle.CreateHybrid(
lifestyleSelector: () => container.GetCurrentLifetimeScope() != null,
trueLifestyle: new LifetimeScopeLifestyle(),
falseLifestyle: new WebRequestLifestyle());
// The created lifestyle can be reused for many registrations.
container.Register<IUserRepository, SqlUserRepository>(hybridLifestyle);
container.Register<ICustomerRepository, SqlCustomerRepository>(hybridLifestyle);
使用此自定义混合生活方式,实例将在活动生命周期范围内存储,但如果没有活动生命周期范围,我们将回退到每个Web请求的缓存实例。如果没有活动的生命周期范围且没有Web请求,an exception will be thrown。
使用Simple Injector,将隐式为您创建Web请求的范围。但就终身范围而言,这是不可能的。这意味着您必须自己明确地开始这样的范围(如图here所示)。这将是be trivial for you,因为您使用命令处理程序。
现在您的问题是lifetime scope和execution context scope之间的区别。两者之间的区别在于生命周期范围是特定于线程的。它不能在可能从线程跳转到线程的异步操作中流动。它使用了ThreadLocal封面。
但是,如果您使用async / wait并从方法返回Task<T>
,则可以使用执行范围。在这种情况下,范围可以放置在不同的线程上,因为它将所有缓存的实例存储在CallContext类中。
在大多数情况下,您将能够在使用生命周期范围的地方使用执行范围,但肯定不是相反。但是,如果您的代码不是异步流动的,那么生命周期范围可以提供更好的性能(尽管可能与执行范围没有显着的性能差异)。