EF Auditing Creations

Хорошо, здесь много чего происходит, и я не хочу утомлять вас, ребята, очень длинным образцом кода, так что вот выдержка...

Когда SaveChangesAsync() вызывается в моем контексте EF, он вызывает этот метод для проверки каждой записи...

async Task Audit(DbEntityEntry<IAmAuditable> entry)
{
    try
    {
        var newAuditEntry = new AuditEntry
        {
            EntityType = entry.Entity.GetType().Name,
            Event = entry.State.ToString(),
            SSOUserId = kernel.Get<User>().Id,
            EntityId = entry.GetId().ToString(),
            EventId = eventId
        };

В случае, если рассматриваемая запись является созданием сущности, которое приведет к вставке в БД, я также делаю это...

var properties = entry.CurrentValues.PropertyNames.Select(p => entry.Property(p)).ToList();
var addedValues = new List<AuditDataItem>();

foreach (var p in properties)
{
    addedValues.Add(new AuditDataItem
    {
        PropertyName = p.Name,
        PreviousValue = null,
        NewValue = p.CurrentValue.ToString()
    });
}
newAuditEntry.Changes = addedValues;
break;

... это то место, где он падает... на тот момент базовый вызов SaveChanges еще не был выполнен, поэтому у рассматриваемой сущности еще нет значения первичного ключа... чистый результат заключается в том, что я зарегистрировать создание объекта без первичного

У кого-нибудь есть предложения по хорошему чистому способу обработки этого, чтобы я мог поместить новое значение первичного ключа в AuditDataItem?

РЕДАКТИРОВАТЬ:

Вот пример того, что я записываю в данный момент как json, это один объект AuditEntry и часть некоторых дочерних строк AuditDataItem...

   {
      "Id": 4,
      "SSOUserId": 1,
      "EventId": "6d862aad-0898-4794-aea0-00af6f2994ff",
      "EntityType": "AC_Programme",
      "Event": "Added",
      "TimeOfEvent": "2016-02-04T12:04:31.5501508+01:00",
      "Changes": [
        {
          "Id": 34,
          "PropertyName": "Id",
          "PreviousValue": null,
          "NewValue": "0"
        },
        {
          "Id": 35,
          "PropertyName": "Name",
          "PreviousValue": null,
          "NewValue": "Test"
        },
        ...
      ]
    }

3 ответа

Решение

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

public override async Task<int> SaveChangesAsync()
{
    try
    {
        await AuditChanges(new[] { EntityState.Modified, EntityState.Deleted });
        var result = await base.SaveChangesAsync();
        await AuditChanges(new[] { EntityState.Added });
        return result;
    }
    catch (DbEntityValidationException ex) { throw ConstructDetailsFor(ex); }
}

async Task AuditChanges(EntityState[] states)
{
    var auditableEntities = ChangeTracker.Entries<IAmAuditable>()
        .Where(e => states.Contains(e.State));

    foreach (var entry in auditableEntities)
        await Audit(entry);
}

async Task Audit(DbEntityEntry<IAmAuditable> entry)
{
    ...

Это так просто, как только можно:)

Затем мой метод аудита в основном входит в оператор switch и решает, какую логику запустить, основываясь на переданной проверяемой записи сущности из трекера изменений.

Я не думаю, что одитинг может стать намного проще, чем этот.

Я помещаю это в базовый базовый класс для всех моих контекстов EF и запускаю миграцию, чтобы применить это ко всем БД и ударам... везде происходит динамический, автоматический аудит всех сущностей, помеченных с помощью "IAmAuditable" (пустой интерфейс маркера).

Я думал об использовании атрибута, но это потребовало бы отражения, а что нет.

Насколько я знаю, нет никаких событий, чтобы перехватить создание объекта, после того, как объект был создан. Есть мероприятия для:

  • ObjectContext.SavingChanges: Происходит, когда изменения сохраняются в источнике данных.
  • ObjectContext.ObjectMaterialized: Происходит, когда новый объект сущности создается из данных в источнике данных как часть запроса или операции загрузки.

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

Единственное решение, о котором я могу подумать, это переопределить исходный SaveChanges и сделать это в переопределенном методе:

  • найдите все объекты в состоянии "добавлено" и сохраните ссылки на них, например, добавив их в List<Object>
  • вызвать изменения сохранения базы, чтобы изменения материализовались в базе данных, а сущности в DbCOntext обновлялись
  • доступ к сущностям в списке, и вы получите все сгенерированные БД свойства (например, рассчитанные, тождества, guid и т. д.), чтобы вы могли правильно их регистрировать

Ведение журнала и перехват операций с базой данных (EF6 и далее) не соответствуют вашим потребностям

Вам нужно сохранить ObjectStateEntry в вашем AuditEntry, а затем повторно посетить каждый AuditEntry, первичный ключ которого был "Временным" в событии PostSaveChanges.

Вот пример:

Очевидно, я рекомендую вам использовать EF+ Audit вместо создания своей собственной библиотеки, но если вы все еще хотите ее кодировать, библиотека с открытым исходным кодом, так что вы сможете найти много информации, которая поможет вам.

Отказ от ответственности: я владелец проекта EF+ (EntityFramework Plus)

Другие вопросы по тегам