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)