偶然发现了这个问题。我的应用没有从数据库返回新数据。
这些似乎是 3 个解决方案:
-
选择时重新加载:首先选择对象,然后重新加载。如果没有缓存,加载两次?
-
使用后分离:如果您忘记在使用后分离一个对象,这将导致应用程序的完全独立部分出现错误,并且极难追踪。
-
在使用后处理 DbContext。绝对是要走的路。
我在 Repository 类中创建了我的 DbContext 实例。如果 DbContext 在存储库级别声明,那么我无法控制它的处置方式。那是不行的。如果我在每次调用时都创建一个新的 DbContext,那么我无法调用 Select、修改数据,然后调用 Update。
似乎我的存储库模式中根本缺少某些东西。
在对基本的存储库模式进行了一些研究后,我找到了解决方案:工作单元模式和存储库模式。
This is an excellent article on the Unit of Work pattern
或this article from Microsoft。我目前拥有的是页面上方的存储库,缺少的是“实现通用存储库和工作单元类”部分
基本上,您不是将存储库注入您的服务,而是通过注入服务的 UnitOfWork 访问所有存储库。它会解决很多问题。
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationContext _context;
public UnitOfWork(ApplicationContext context)
{
_context = context;
Developers = new DeveloperRepository(_context);
Projects = new ProjectRepository(_context);
}
public IDeveloperRepository Developers { get; private set; }
public IProjectRepository Projects { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
剩下的问题是:如何创建 IUnitOfWork 实例?
如果我在类构造函数中创建它以像存储库一样注入它,那么它会以完全相同的方式创建和销毁,我们又回到了同样的问题。在 ASP.NET 和 MVC 中,类实例的生命周期很短,因此在构造函数中注入可能没问题,但在 Blazor 和桌面应用程序中,类实例的生命周期要长得多,而且更成问题。
This article from Microsoft 明确指出依赖注入不适合在 Blazor 中管理 DbContext 的生命周期:
在 Blazor Server 应用中,范围服务注册可能会出现问题
因为该实例是在用户的组件之间共享的
电路。 DbContext 不是线程安全的,也不是为并发设计的
采用。由于以下原因,现有的生命周期是不合适的:
- Singleton 在应用程序的所有用户之间共享状态并导致
不适当的同时使用。
- Scoped(默认)构成类似
同一用户的组件之间的问题。
- 瞬态导致新的
每个请求的实例;但由于组件可以长期使用,这
导致上下文的寿命比预期的要长。
他们建议使用工厂模式,可以这样实现
/// <summary>
/// Creates instances of UnitOfWork. Repositories and UnitOfWork are not automatically injected through dependency injection,
/// and this class is the only one injected into classes to give access to the rest.
/// </summary>
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly IDateTimeService _dateService;
private readonly DbContextOptions<PaymentsContext> _options;
public UnitOfWorkFactory(IDateTimeService dateService, DbContextOptions<PaymentsContext> options)
{
_dateService = dateService;
_options = options;
}
/// <summary>
/// Creates a new Unit of Work, which can be viewed as a transaction. It provides access to all data repositories.
/// </summary>
/// <returns>The new Unit of Work.</returns>
public IUnitOfWork Create() => new UnitOfWork(CreateContext(), _dateService);
/// <summary>
/// Creates a new DbContext.
/// </summary>
/// <returns>The new DbContext.</returns>
public PaymentsContext CreateContext() => new(_options);
}
IWorkOfUnit 和任何存储库都不会注册到 IoC 容器中。只有 IWorkOfUnitFactory。
最后...如何在各种服务之间共享事务?
我有一个 SetStatus 方法来更新数据库中的状态字段。这种方法应该如何知道它是独立操作还是更大事务的一部分?
由于类级别的依赖注入不适合管理和共享单元的工作,那么唯一的选择就是将其作为参数传递给需要它的方法。
我为每个需要它的方法添加一个可选的IUnitOfWork? workScope = null 参数,并且仅当此参数为空时才调用 Save。这是一个实现。
public virtual async Task<TempOrder?> SetStatusAsync(int orderId, PaymentStatus status, IUnitOfWork? workScope = null)
{
using var unitOfWork = _workFactory.Create();
var work = workScope ?? unitOfWork;
var order = await work.Orders.GetByIdAsync(orderId);
if (order != null)
{
order.Status = status;
work.Orders.Update(order); // DateModified gets set here
if (workScope == null)
{
await work.SaveAsync();
}
}
return order;
}
另一种选择是让 IUnitOfWorkFactory.Create 采用 workScope 参数,并在设置时:
- 重用现有的 DbContext
- 请勿丢弃
- IUnitOfWork.Save 不会提交
我的最终实现可以这样使用
public virtual async Task<TempOrder?> SetStatusAsync(int orderId, PaymentStatus status, IUnitOfWork? workScope = null)
{
using var unitOfWork = _workFactory.Create(workScope);
var order = await unitOfWork.Orders.GetByIdAsync(orderId);
if (order != null)
{
order.Status = status;
work.Orders.Update(order); // DateModified gets set here
await unitOfWork.SaveAsync(); // Ignored if workScope != null
}
return order;
}
呸!那个虫子是一个兔子洞。这是一个相当长的解决方案,但应该通过可靠的架构来解决它。