Написание службы оповещений на основе событий с использованием DBContext ChangeTracker - разделение задач - PullRequest
0 голосов
/ 27 марта 2019

У меня есть контроллер, который изменяет встречи в календаре.Я хочу использовать свой концентратор SignalR для уведомления пользователей: «Пользователь X изменен {назначение_Титла}: список: {Свойство} {OriginalValue} {NewValue}» *

Я новичок в C # (по синтаксису этохорошо, но концепции ООП являются новыми);Я пытаюсь использовать события для достижения вышеизложенного.Ниже приведены обработчики и аргументы, выписка из контроллера и краткое изложение моих вопросов.

Код сокращен!

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 в аргументах события?
  • для каждой измененной записи, я могуполучить _dbContext.Entry(modifiedEntry).Properties.Where(x => x.IsModified).ToList(); - но относится ли это к классу NotificationService?Для этого мне также нужно передать DbContext в NotificationService.
  • Может ли быть более простой способ добиться этого?Добавлять и удалять обработчики легко («Пользователь X добавил | удалено ... назначение {Заголовок}»), ​​но чтобы выяснить точные изменения, мне нужно взглянуть на измененные свойства.

Буду признателен, если бы вы смогли дать представление о том, как вы будете структурировать и выполнять эту задачу.Спасибо.

1 Ответ

1 голос
/ 27 марта 2019

Для начала я бы порекомендовал вам , а не , чтобы использовать события здесь.События - это то, что может показаться очень полезным, но из-за того, как они работают (синхронно), они на самом деле не лучший способ достичь этого в веб-контексте, особенно в асинхронной среде, такой как 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 и вызываете реальный метод для него.Таким образом, у вас полностью отключены контроллер и служба уведомлений: контроллеру не нужно сначала создавать тип, и вам также не нужно подписываться на некоторые события (от которых вам также придется отказаться от подписки).в какой-то момент снова, кстати).И вы можете оставить экземпляр полностью в контейнере 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, и вы будете намного более гибкими в долгосрочной перспективе.

...