将上下文与数据库同步

问题描述 投票:0回答:1

桌面; 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();
    }
}
entity-framework mvvm repository-pattern unit-of-work
1个回答
0
投票

使用 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。如果列表中有任何更改,我们会刷新当前页面。

基于推送的系统更适合大型多客户端设置,但如果您想避免在收到订阅的事件通知时启动完全刷新,则测试可能会比较棘手,而且编写起来也很复杂。基于拉的系统测试起来很简单,但可能会与数据库“闲聊”,尤其是在轮询周期较短的情况下。 (提前查看更改)

© www.soinside.com 2019 - 2024. All rights reserved.