使用DBContext ChangeTracker编写基于事件的SignalR Notification Service - 关注点分离

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用DBContext ChangeTracker编写基于事件的SignalR Notification Service - 关注点分离相关的知识,希望对你有一定的参考价值。

我有一个控制器,可以修改日历中的约会。我想使用我的SignalR中心通知用户“用户X已更改{appointmentTitle}:列表:{Property} {OriginalValue} {NewValue}”

我是C#的初学者(语法方面没问题,但是OOP概念是新的);我正在尝试使用事件来实现上述目标。下面是处理程序和参数,控制器的摘录和我的问题摘要。

代码缩写!

EventArgs的

    public class AppointmentChangeEventArgs : EventArgs
    {
        public EntityState AppointmentState = EntityState.Unchanged;
        public EntityEntry Entity = null;
        public ScheduleData Appointment = null;
    }

事件处理程序

    // maybe this could be just one, and let the consumer decide based on EntityState?
    public EventHandler<AppointmentChangeEventArgs> AppointmentChanged;
    public EventHandler<AppointmentChangeEventArgs> AppointmentAdded;
    public EventHandler<AppointmentChangeEventArgs> AppointmentRemoved;

    protected virtual void OnAppointment(AppointmentChangeEventArgs appointmentChangeEventArgs)
    {
        switch (appointmentChangeEventArgs.AppointmentState)
        {
            case EntityState.Added:
                AppointmentAdded?.Invoke(this, appointmentChangeEventArgs);
                break;
            case EntityState.Deleted:
                AppointmentRemoved?.Invoke(this, appointmentChangeEventArgs);
                break;
            case EntityState.Modified:
                AppointmentChanged?.Invoke(this, appointmentChangeEventArgs);
                break;
            default:
                break;
        }
    }

调节器

public async Task<IActionResult> Batch([FromBody] ScheduleEditParameters param)
    switch (param.Action) {
        case "insert":
             await _dbContext.Appointments.AddAsync(appointment);
             break;
        case "update":
             // .. get Appointment from DB
             appointment.Subject = value.Subject;
             appointment.StartTime = value.StartTime;
             // ...
        case "remove": 
             // .. get Appointment from DB
             _dbContext.Appointments.Remove(appointment);
    }
    var modifiedEntries = _dbContext.ChangeTracker
            .Entries()
            .Where(x => x.State != EntityState.Unchanged && x.State != EntityState.Detached)
            .Select(x => new AppointmentChangeEventArgs() { Entity  = (EntityEntry) x.Entity, AppointmentState = x.State, Appointment = appointment })
            .ToList();

        if (modifiedEntries.Any())
        {
            var notificationService = new NotificationService(signalRHub, notificationLogger);
            AppointmentAdded += notificationService.OnAppointmentChanged;
            AppointmentChanged += notificationService.OnAppointmentChanged;
            AppointmentRemoved += notificationService.OnAppointmentChanged;
        }
        await _dbContext.SaveChangesAsync();

问题

  • 在事件参数中使用EntityEntry和EntityState是否可以?
  • 对于每个修改过的Entry,我可以获得_dbContext.Entry(modifiedEntry).Properties.Where(x => x.IsModified).ToList(); - 但这是否属于NotificationService类?为了做到这一点,我还需要将DbContext传递给NotificationService。
  • 有没有更简单的方法来实现这一目标?添加和删​​除处理程序很容易(“用户X已添加|删除...约会{标题}”),但为了找出确切的更改,我必须查看修改后的属性。

如果您能够深入了解如何构建和处理此任务,我将不胜感激。谢谢。

答案

首先,我建议你不要在这里使用事件。事件可能听起来非常有用,但由于它们的工作方式(同步),它们实际上并不是在Web上下文中实现此目的的最佳方式,尤其是在像ASP.NET Core这样的主要异步框架中。

相反,我建议您简单地声明自己的类型,例如IAppointmentChangeHandler像这样:

public interface IAppointmentChangeHandler
{
    Task AddAppointment(ScheduleData appointment);
    Task UpdateAppointment(ScheduleData appointment);
    Task RemoveAppointment(ScheduleData appointment);
}

您的NotificationService可以实现该接口以便能够处理这些事件(显然只需发送您需要发送的任何内容):

public class NotificationService : IAppointmentChangeHandler
{
    private readonly IHubContext _hubContext;

    public NotificationService(IHubContext hubContext)
    {
        _hubContext = hubContext;
    }

    public AddAppointment(ScheduleData appointment)
    {
        await _hubContext.Clients.InvokeAsync("AddAppointment", appointment);
    }

    public UpdateAppointment(ScheduleData appointment)
    {
        await _hubContext.Clients.InvokeAsync("UpdateAppointment", appointment);
    }

    public RemoveAppointment(ScheduleData appointment)
    {
        await _hubContext.Clients.InvokeAsync("RemoveAppointment", appointment);
    }
}

在你的控制器内部,你只需注入那个IAppointmentChangeHandler然后调用它上面的实际方法。这样你就可以完全解耦控制器和通知服务了:控制器不需要先构造类型,你也不需要订阅某些事件(你也可以在某些时候取消订阅btw) 。您可以将实例完全保留到DI容器中。


回答你的个人问题:

在事件参数中使用EntityEntry和EntityState是否可以?

我会避免在数据库之外的上下文中使用它。两者都是数据库设置的实现细节,因为您在此处使用Entity Framework。这不仅会将您的事件处理程序强烈地与Entity Framework结合在一起(意味着每个想成为事件处理程序的人都需要引用EF,即使他们没有对它做任何事情),您也可能泄漏可能发生更改的内部状态之后(你没有拥有EntityEntry所以谁知道EF后来做了什么)。

对于每个修改过的条目,我都可以获得_dbContext.Entry(modifiedEntry).Properties.Where(x => x.IsModified).ToList();

如果你查看你的代码,你首先在你的数据库集上调用AddUpdateRemove;然后你正在使用一些逻辑来查看一些内部EF的东西,以找出真正完全相同的东西。如果你直接在这三个AppointmentChangeEventArgs案例中构建switch,你可以使这个复杂得多。

但这属于NotificationService类吗?为了做到这一点,我还需要将DbContext传递给NotificationService。

通知服务是否与数据库有关?我会说不;除非您将这些通知持久化到数据库中。当我考虑通知服务时,我希望能够在其上调用某些内容来主动触发通知,而不是在服务中使用某些逻辑来确定它可能触发的通知。

有没有更简单的方法来实现这一目标?添加和删​​除处理程序很容易(“用户X已添加|删除...约会{标题}”),但为了找出确切的更改,我必须查看修改后的属性。

首先考虑最简单的方法:在哪里更新数据库实体的值?在那个update案件中。因此,此时,您要复制传递对象的值,您还可以检查实际更改的属性。有了它,您可以轻松记录您需要通知的属性。

将此完全与EF分离,从长远来看,您将更加灵活。

以上是关于使用DBContext ChangeTracker编写基于事件的SignalR Notification Service - 关注点分离的主要内容,如果未能解决你的问题,请参考以下文章

ef保存文件到指定目录

当实体集 ChangeTracker 发生更改时如何通知 UI

EF ChangeTracker 访问被跟踪实体及其导航集合

使用 ObjectContext 获取关系记录的主键

.NetCore 下使用多个DbContext

如何在控制器中使用多个 DBContext