The application I am currently working on has a requirement to audit which application user last created or updated database records. All tables in the database are required to have an nvarchar column UserName
.
I didn’t want this concern to leak into my application. After some investigation I discovered that ObjectContext
has the SavingChanges
event that would be ideal for my purposes.
So the creation of my ObjectContext
becomes
var entities = new MyEntities(); entities.SavingChanges += SetUserNameEvent;
I originally thought that SetUserNameEvent
would have to use reflection to obtain and set the UserName property. However I found a way to use T4 to generate code resulting in all entities with the UserName
property implementing a common interface (IUserNameStamped
). I’ve written a blog post talking about about the T4 code.
So with all my entities implementing this common interface, SetUserNameEvent
is then
/// <summary> /// Sets the user name of all added and modified /// entities to the username provided by /// the <see cref="UserNameProvider"/>. /// </summary> private void SetUserNameEvent(object sender, EventArgs e) { Contract.Requires<ArgumentException>( sender is ObjectContext, "sender is not an instance of ObjectContext"); var objectContext = (ObjectContext)sender; foreach (ObjectStateEntry entry in objectContext.ObjectStateManager.GetObjectStateEntries( EntityState.Added | EntityState.Modified)) { var stamped = entry.Entity as IUserNameStamped; Contract.Assert(stamped != null, "Expected all entities implement IUserNameStamped"); stamped.UserName = UserNameProvider.UserName; } }
So here, we get all added and modified entries from the ObjectStateManager
, and use these to obtain the entities and set their UserName
. UserNameProvider is an abstraction used as I have several applications utilising my object context, each with a different way to obtain the current application user. Note that my code is using Code Contracts.
One complication I’ve found is with child entities. Sometimes, I’ve found I have to add the child entity both to its parent object and to the object context, but sometimes it’s enough to simply add the child entity to it’s parent object. That is:
var entities = ObjectContextFactory.GetObjectContext(); var childEntity = new ChildEntity(); entities.ParentEntities.First().ChildEntities.Add(childEntity); // entities.ChildEntities.AddObject(childEntity); entities.SaveChanges() // Sometimes UserName will not get set without the commented line above, // resulting in a NOT NULL constraint violation
I’ve found no rhyme or reason as to why the addition to the ObjectContext
is only sometimes required, I’d love hints as to why this is.
Note I’m actually using the unit of work pattern for my application, and I use a unit of work factory rather than an object context factory, but that’s irrelevant to the use of the SavingChanges
event in this fashion.
Coincidentally, we do much the same on my current project, except that not all entities have a user name. The interface is therefore conditionally included by T4 depending on the existence of a LastModifiedByUsername property.
Have not run into your problem with child entities (we have a good number of them: 50 at least). As a wild guess, I have occasionally seen strange inconsistencies when one-to-one relationships are involved rather than one-to-many.
LikeLike
Are you using the POCO T4 templates? http://visualstudiogallery.msdn.microsoft.com/23df0450-5677-4926-96cc-173d02752313
I suspect that there’s some oddity specific to POCO that I’m encountering.
LikeLike