桌面; WPF + MVVM、Prism、实体框架、UnitOfWork + 存储库。 UnitOfWork 同时在两台计算机上初始化。
someUser
已添加到第一台 PC 上。
如何在第二台 PC 上的
_unitOfWork.Users
中获取某个用户?对于视图来说,视图模型只创建一次,对吗?因此 UnitOfWork 内部的上下文不知道新的 someUser
。
public class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
private IUserRepository _users
public IUserRepository Users => _users ??= new UserRepository(_context);
public UnitOfWork(MyDbContext context)
{
_context = context;
}
public async Task CommitAsync()
{
await _context.SaveChangesAsync();
}
public Task Rollback()
{
return Task.CompletedTask;
}
}
public class UsersViewModel : BindableBase
{
private readonly IUnitOfWork _unitOfWork;
public UsersViewModel (IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task Foo()
{
_unitOfWork.Users.Add(someUser);
_unitOfWork.CommitAsync();
}
}
使用 WPF,您不想依赖 DbContext 的构造函数注入。这对于 ASP.Net MVC/Razor 页面效果很好,因为控制器等的范围是每个请求的,因此 DbContext 仅在处理每个请求时“活动”。在 WPF 应用程序中,类的实例的存活时间将相当长。您应该考虑注入 DbContextFactory 类或使用充当 DbContext 周围范围的 UoW 模式,而不是注入 DbContext(或包装 DbContext 的工作单元)。 (即 Zejji.DbContextScope)
长时间运行的 DbContext 不好,因为默认情况下 DbContext 对所有实体读取使用跟踪缓存。跟踪缓存“不是”为了性能,它们是为了更改跟踪和引用跟踪。 (以避免同一记录有 2 个或更多不同的对象实例)跟踪的实体越多,DbContext 的速度就越慢,并且您还会遇到过时的数据情况。 (2 个客户端“读取”同一记录,1 个客户端更新该行并保存,第二个客户端“读取”但获取其缓存副本,看不到其他客户端的更改。) 您可以通过使用
AsNoTracking()
查询来缓解上述情况。例如,如果您使用:
context.Users.AsNoTracking()
客户端 B 将从数据库中获取当前数据状态,并避免将用户实例添加到其跟踪缓存中。除非您实施集中式消息传递系统,否则单独的应用程序不会收到有关新插入和更新的通知。 (请参阅:发布/订阅库,例如 Prism 中的 EventAggregator 或消息队列,例如 RabbitMQ)
EF 没有内置对基于推送的通知的支持,您需要自己合并合适的解决方案。例如,如果一个客户端插入新用户或更新用户,它会向消息总线发布一条消息,其中显示“{Event:UserInserted, Id:10234}”或“{Event:UserUpdated, Id:10234}”,其中每个应用程序都订阅事件取决于您所在的视图。例如,如果另一个客户端位于“用户列表”页面上,并且它订阅并接收 UserInserted 或 UserUpdated 事件,它将采取诸如刷新用户列表或重新加载/更新用户行之类的操作。或者,您可以使用基于轮询的系统(拉),在后台您的页面定期请求自上次轮询时间戳以来已更改的用户行。例如,如果您在带有过滤器的搜索页面上查询附加时间戳检查的过滤器:
var dataChanged = context.Users
.Where( /* build your normal Where clause based on search criteria */)
.WHere(x => x.LastModifiedDateTime >= _lastPolledDateTime)
.Any();
_lastPolledDateTime = DateTime.Now();
if (dataChanged)
// Reload your search results.
如果结果中的任何行发生更改,或者可能包含新记录,这将触发页面刷新。如果您使用分页(跳过/获取),那么您需要获取当前页面的 ID 并将其与当前页面 ID 列表进行比较,如果 ID 列表发生更改则重新加载页面。即:
var updatedIds = context.Users
.Where( /* build your normal Where clause based on search criteria */)
.OrderBy( /* order criteria */)
.Select(x => x.Id)
.Skip(_currentPage * _pageSize)
.Take(_pageSize)
.ToList();
if (_currentIds.Count != updatedIds.Count || _currentIds.Except(updatedIds).Any())
// Reload your search results.
页面保存当前显示的行页面的当前 ID 列表。我们仅轮询当前页面的 ID。如果列表中有任何更改,我们会刷新当前页面。
基于推送的系统更适合大型多客户端设置,但如果您想避免在收到订阅的事件通知时启动完全刷新,则测试可能会比较棘手,而且编写起来也很复杂。基于拉的系统测试起来很简单,但可能会与数据库“闲聊”,尤其是在轮询周期较短的情况下。 (提前查看更改)