使用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();
如果你查看你的代码,你首先在你的数据库集上调用Add
,Update
或Remove
;然后你正在使用一些逻辑来查看一些内部EF的东西,以找出真正完全相同的东西。如果你直接在这三个AppointmentChangeEventArgs
案例中构建switch
,你可以使这个复杂得多。
但这属于NotificationService类吗?为了做到这一点,我还需要将DbContext传递给NotificationService。
通知服务是否与数据库有关?我会说不;除非您将这些通知持久化到数据库中。当我考虑通知服务时,我希望能够在其上调用某些内容来主动触发通知,而不是在服务中使用某些逻辑来确定它可能触发的通知。
有没有更简单的方法来实现这一目标?添加和删除处理程序很容易(“用户X已添加|删除...约会{标题}”),但为了找出确切的更改,我必须查看修改后的属性。
首先考虑最简单的方法:在哪里更新数据库实体的值?在那个update
案件中。因此,此时,您要复制传递对象的值,您还可以检查实际更改的属性。有了它,您可以轻松记录您需要通知的属性。
将此完全与EF分离,从长远来看,您将更加灵活。
以上是关于使用DBContext ChangeTracker编写基于事件的SignalR Notification Service - 关注点分离的主要内容,如果未能解决你的问题,请参考以下文章
当实体集 ChangeTracker 发生更改时如何通知 UI