如何在与应用程序类型无关的NTier Api中提供适当作用域的DbContext
本文关键字:DbContext 作用域 Api NTier 应用程序 类型 | 更新日期: 2023-09-27 18:09:01
我正在使用EF Code First编写一个类库,我希望在所有类型的应用程序中使用,包括web,控制台和桌面。目前,我的API仅作为web API存在,它使用标准工厂模式来返回作用域为HttpContext的DbContext实例。
方法调用看起来像这样:
EntityDbContextFactory<MyDbContext>.GetInstance();
和实现:
public class EntityDbContextFactory<TContext>
where TContext : class, IDisposable, new()
{
private static TContext _dbContext;
public static TContext GetInstance()
{
TContext context;
if (HttpContext.Current != null)
{
var objectContextKey = HttpContext.Current.GetHashCode().ToString("x") +
typeof(TContext).GetHashCode().ToString(CultureInfo.InvariantCulture);
if (!HttpContext.Current.Items.Contains(objectContextKey))
{
context = new TContext();
HttpContext.Current.Items.Add(objectContextKey, context);
}
else
{
context = HttpContext.Current.Items[objectContextKey] as TContext;
}
}
else
{
if (_dbContext == null)
{
_dbContext = new TContext();
context = _dbContext;
}
else
{
context = _dbContext;
}
}
return context;
}
}
非常标准的东西,我在网上很多地方看到过。我需要手动提供这种作用域,因为我不能保证使用我的API的程序员使用的是DI容器,即使我可以,我也不希望我的存储库跟踪状态(通过将DbContext注入到存储库构造函数中,就像通常看到的那样),也不希望调用我的服务的人关心我的数据访问方法。
现在的问题是,我如何将其扩展到主机和桌面应用?如果我在这些应用程序中使用上述内容,我必须参考System。Web,这看起来一点也不合适。
标准的单例模式可以适用于桌面和控制台,但如果其中任何一个应用程序长时间打开,那么缓存在上下文中的实体很可能会变得陈旧。我当然不想为应用的生命周期做完全的单例方法,但如何界定它们呢?如果可能的话,我希望能够仍然有效地创建DbContexts(以及服务和存储库创建),而不是每次都创建新的。并且不依赖于外部库
这看起来很傻,但我几乎已经在这么做了。正如评论中的链接所提到的,你根本不希望在控制台或桌面应用中保持上下文存活,因为这些应用是有状态的,上下文可能很快就会过时。对于那些实例,你应该在每次调用时使用一个新实例。
然而,在没有状态的web应用程序中,将DbContext作用域设置为HttpContext (Per web Request)是适当的和常见的做法。每个请求可能包含多个服务调用,因此每个请求只使用一个dbcontext实例是有意义的。几乎可以把它想象成一个交易。微软本身就有很多例子证明了这一点。
然而,我的方法有一个额外的好处。您在网上看到的大多数使用这种方法的NTier示例都将dbcontext存储为存储库类的成员。这使得你的NTier API是有状态的,然后根据需要(因为dbcontext不是线程安全的),你必须将你的存储库和服务类限定为每个请求一个实例。但是使用我的方法,你可以将你的服务和存储库类限定为单例,这更有效。
网上常见的场景:
public class UserRepository
{
private MyDbContext _myDbContext;
public UserRepository(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
}
DbContext不是线程安全的,因此你的应用中的每一层(通常是DbContext -> repository -> service)都必须限定在你的DI容器中的每个http请求的范围内。
我的方法:
public class UserRepository<TbContext>
{
private DbContext Context
{
get { return EntityContextFactory<TDbContext>.Current(); }
}
}
在工厂类的问题中使用上述代码。现在我的服务和存储库可以是单例的,不管我在什么样的应用程序中使用我的API,它都会适当地处理dbcontext的生命周期。唯一需要改变的是,如果HttpContext是空的,返回一个新的实例,不要将其存储为私有成员:
public class EntityContextFactory<TContext>
where TContext: class, IDisposable, new()
{
private static TContext _dbContext;
public static TContext Current()
{
TContext context = null;
if (HttpContext.Current != null)
{
string objectContextKey = HttpContext.Current.GetHashCode().ToString("x") +
typeof (TContext).GetHashCode().ToString(CultureInfo.InvariantCulture);
if (HttpContext.Current.Items.Contains(objectContextKey) == false)
{
context = new TContext();
HttpContext.Current.Items.Add(objectContextKey, context);
}
else
{
context = HttpContext.Current.Items[objectContextKey] as TContext;
}
}
else
{
context = new TContext();
}
return context;
}
}