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

时间:2019-03-27 16:06:43

标签: asp.net-core entity-framework-core asp.net-core-signalr

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

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

代码缩写!

EventArgs

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

EventHandler

    // 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已添加|删除...约会{Title}”),但是为了弄清楚确切的变化,我必须查看修改后的属性。

如果您能提供有关如何组织和处理此任务的见解,我将不胜感激。谢谢。

1 个答案:

答案 0 :(得分:1)

首先,我通常建议您在此处使用事件。事件听起来可能很有用,但是由于它们(同步)的工作方式,它们并不是在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,然后在其上调用实际方法。这样,您就可以将控制器和通知服务完全解耦:控制器不需要先 construct 类型,并且您也不需要预订某些事件(也必须取消订阅)从某个时候再次顺便说一句)。您可以将实例完全留给DI容器。


要回答您的个人问题:

  

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

我会避免在数据库外部的上下文中使用它。两者都是数据库设置的实现细节,因为您在这里使用实体框架。这不仅会使您的事件处理程序与Entity Framework紧密地结合在一起(这意味着每个想要成为事件处理程序的人都需要引用EF,即使他们没有对它做任何事情),而且您还在泄漏可能改变的内部状态稍后(您不拥有EntityEntry的{​​{1}},那么谁知道之后EF会做什么)。

  

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

如果您查看代码,则首先要在数据库集上调用AddUpdateRemove;然后您使用某种逻辑来查看某些内部 EF东西,以找出确实完全相同的东西。如果直接在这三个AppointmentChangeEventArgs情况下构造switch,则可以使事情复杂度降低。

  

但这是否属于NotificationService类?为此,我还需要将DbContext传递给NotificationService。

通知服务与数据库有关吗?我会说不。除非您将这些通知持久保存到数据库中。当我想到通知服务时,我希望能够在其上调用某些东西来主动触发通知,而不是在服务中使用某种逻辑来弄清楚它可能触发哪些通知。

  

有没有更简单的方法可以实现这一目标?添加和删​​除处理程序很容易(“用户X已添加|删除...约会{Title}”),但是要弄清楚确切的变化,我必须查看修改后的属性。

首先以最简单的方式考虑它:在哪里更新数据库实体的值?在update情况下。因此,从复制的对象中复制值时,也可以只检查实际更改的属性。这样,您就可以轻松记录需要通知的属性。

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