EF Core - как проверить след с объектами значения - PullRequest
6 голосов
/ 31 марта 2020

Я пытаюсь реализовать журнал аудита (отслеживать, что изменилось, когда и кем) для выбора классов в Entity Framework Core.

Моя текущая реализация опирается на переопределение OnSaveChangesAsyn c:

public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) 
{
    var currentUserFullName = _userService.CurrentUserFullName!;

    foreach (var entry in ChangeTracker.Entries<AuditableEntity>())  
    {
        switch (entry.State) 
        {
            case EntityState.Added:
                    entry.Entity.CreatedBy = currentUserFullName;
                    entry.Entity.Created = _dateTime.Now;
                    break;

            case EntityState.Modified:
                    var originalValues = new Dictionary<string, object?>();
                    var currentValues = new Dictionary<string, object?>();

                    foreach (var prop in entry.Properties.Where(p => p.IsModified)) 
                    {
                        var name = prop.Metadata.Name;
                        originalValues.Add(name, prop.OriginalValue);
                        currentValues.Add(name, prop.CurrentValue);
                    }
                    entry.Entity.LastModifiedBy = currentUserFullName;
                    entry.Entity.LastModified = _dateTime.Now;

                    entry.Entity.LogEntries.Add(
                        new EntityEvent(
                            _dateTime.Now,
                            JsonConvert.SerializeObject(originalValues),
                            JsonConvert.SerializeObject(currentValues),
                            currentUserFullName));
                    break;
            }
        }

        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}

Это простой, чистый и очень простой в использовании; любая сущность, для которой требуется контрольный журнал, должна только унаследовать AuditableEntity.

Однако у этого подхода есть серьезное ограничение: он не может фиксировать изменения, внесенные в свойства навигации.

Наши сущности делают хорошее использование объектов-значений, таких как адрес электронной почты:

public class EmailAddress : ValueObjectBase
{
    public string Value { get; private set; } = null!;

    public static EmailAddress Create(string value) 
    {
        if (!IsValidEmail(value)) 
        {
            throw new ArgumentException("Incorrect email address format", nameof(value));
        }

        return new EmailAddress {
            Value = value
        };
    }
}

... Entity.cs

public EmailAddress Email { get; set; }   

... Entity EF configuration
entity.OwnsOne(e => e.Email);

... Updating
entity.Email = EmailAddress.Create("e@mail.com");

Теперь, если пользователь изменяет адрес электронной почты этой сущности, состояние сущности никогда не изменяется на измененное. Кажется, что EF Core обрабатывает объекты ValueObject как свойства навигации, которые обрабатываются отдельно.

Поэтому я думаю, что есть несколько вариантов:

  1. Прекратить использование объектов ValueObject в качестве свойств объекта. Мы все еще можем использовать их как параметры конструктора сущностей, но это приведет к каскадной сложности для остальной части кода. Это также уменьшит доверие к достоверности данных sh.

  2. Прекратите использование SaveChangesAsyn c и создайте собственную обработку для аудита. Опять же, это вызовет дополнительную сложность в архитектуре и, вероятно, будет менее производительным.

  3. Некоторое странное хакерство для ChangeTracker - это звучит рискованно, но может сработать теоретически

  4. Что-то еще, что?

Ответы [ 2 ]

2 голосов
/ 31 марта 2020

В случае, когда значения объектов сопоставляются с одним столбцом в базе данных (например, адрес электронной почты хранится в текстовом столбце), вы можете вместо этого использовать преобразователи:

var emailAddressConverter = new ValueConverter<EmailAddress, string>(
    emailAddress => emailAddress.Value,
    @string => EmailAddress.Create(@string));

modelBuilder.Entity<User>()
    .Property(user => user.Email)
    .HasConversion(emailAddressConverter);

Это должен хорошо работать с вашим кодом отслеживания изменений.

0 голосов
/ 31 марта 2020

Возможно, существует более эффективный метод, но вы можете перечислить набор принадлежащих записей;

entry.References.Where(r =>
    r.TargetEntry != null
    && r.TargetEntry.State == EntryState.Modified
    && r.TargetEntry.Metadata.IsOwned())

Если родительский объект не изменен или изменен, то также отследите их изменения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...