Reputation: 8628
Ok there's a lot going on here and I don't want to bore you guys with a very long winded code sample so here's an extract ...
When SaveChangesAsync() is called on my EF context I have it calling this method to audit each entry ...
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
};
In the event that the entry in question is an entity creation which will result in an insert on the db i then also do this ...
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;
... this is where it falls over ... at that point in time the base call to SaveChanges hasn't yet been executed so the entity in question does not yet have a primary key value ... the net result is that I log the creation of an entity with no primary.
Does anyone have suggestions on a nice clean way to handle this so I can put the new primary key value in to an AuditDataItem?
EDIT:
Here's an example of what I am logging at the moment as json, this is a single AuditEntry object and a partial of some of the child AuditDataItem rows ...
{
"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"
},
...
]
}
Upvotes: 1
Views: 1697
Reputation: 11337
You need to keep the ObjectStateEntry in your AuditEntry then revisit every AuditEntry which the primary key was "Temporary" in a PostSaveChanges event.
Here is an example:
Obviously I recommend you to use EF+ Audit over creating your own library but if you still want to code it, the library is open source so you will be able to find a lot of information to help you.
Disclaimer: I'm the owner of the project EF+ (EntityFramework Plus)
Upvotes: 1
Reputation: 8628
Ok so here's what i came up with ... curious to know what you guys think ...
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)
{
...
This is about as simple as it gets :)
My audit method then basically enters in to a switch statement and decides what logic to run based on the passed in auditable entity entry from the change tracker.
I don't think auditing could get much simpler than this.
I put this in to a generic base class for all my EF contexts and run a migration to apply this to all db's and bang ... everywhere gets dynamic, auto auditing on all entities that are marked with "IAmAuditable" (an empty marker interface).
I thought about using an attribute but that would require reflection and what not.
Upvotes: 0
Reputation: 39015
As far as I know there are no events to intercetp object creation, after the object has been created. There are events for:
The first happens before the changes are saved (same problem you have). The second one happens when an entity is read from the database as result of a query or .Load
, so it doesn't fit your case.
The only solution I can think of is that you override the original SaveChanges, and do this in the overridden method:
List<Object>
The Logging and Intercepting Database Operations (EF6 Onwards) doesn't fit your needs
Upvotes: 1