Auditing activities using the Entity Framework

auditing entity entity-framework

Question

Every row in my database contains a "LastModifiedUser" column, which I want to utilize to gather the logged-in user from an application that makes a modification. Since I am not referring to the database user, this is basically simply a string on each object. In order to save other developers from having to remember to assign it each time they instantiate the entity, I would want to figure out a method to default this for each entity.

So, something similar to this might happen:

using (EntityContext ctx = new EntityContext())
{
    MyEntity foo = new MyEntity();

    // Trying to avoid having the following line every time
    // a new entity is created/added.
    foo.LastModifiedUser = Lookupuser(); 

    ctx.Foos.Addobject(foo);
    ctx.SaveChanges();
}
1
11
9/28/2010 3:34:26 AM

Popular Answer

By using ObjectStateManager, there is a suitable method to do this in EF 4.0.

Create a partial class for your ObjectContext first, then subscribe to Event ObjectContext.SavingChanges. The OnContextCreated Method is the ideal location to subscribe to this event. This method, which is a partial method without an implementation, is called by the constructor of the context object and constructor overloads:

partial void OnContextCreated() {
    this.SavingChanges += Context_SavingChanges;
}


Here is the code that will really perform the job:

void Context_SavingChanges(object sender, EventArgs e) {

    IEnumerable<ObjectStateEntry> objectStateEntries = 
        from ose 
        in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added 
                                                         | EntityState.Modified)
        where ose.Entity != null
        select ose;

    foreach (ObjectStateEntry entry in objectStateEntries) {

        ReadOnlyCollection<FieldMetadata> fieldsMetaData = entry.CurrentValues
                .DataRecordInfo.FieldMetadata;

        FieldMetadata modifiedField = fieldsMetaData
            .Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();

        if (modifiedField.FieldType != null) {

            string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;                    
            if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
                entry.CurrentValues.SetString(modifiedField.Ordinal, Lookupuser());
            }
        }
    }
}

Code Definition:
This code finds any Added or Modified entries with a LastModifiedUser property and changes it with the value returned by your unique Lookupuser() function.

The query essentially drills down to the CurrentValues of each element in the foreach section. Then, using the Where approach, it scans each FieldMetaData item's name to find those whose Name is LastModifiedUser, ignoring all other entries. The if statement then modifies the field's value after confirming that the LastModifiedUser property is a String field.

Instead of subscribing to the SavingChanges event, you may also connect this method by overriding the Method ObjectContext.SaveChanges.

By the way, the code above is from her Entity Framework for Programming book and belongs to Ingrid Lerman.


Implementation of EDIT for Self Tracking POCO:

If you have self-tracking POCOs, I recommend changing the T4 template such that it calls the OnContextCreated() function first. The only thing we need to do is alter the ObjectContext.tt file to look like this: If you check at your ObjectContext.tt file, there is a Initialize() function that is called by all constructors and is a suitable candidate to call our OnContextCreated() method.

private void Initialize()
{
    // Creating proxies requires the use of the ProxyDataContractResolver and
    // may allow lazy loading which can expand the loaded graph during serialization.
    ContextOptions.ProxyCreationEnabled = false;
    ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized);
    // We call our custom method here:
    OnContextCreated();
}

And as a result, when the Context is created, our OnContextCreated() function will be invoked.

Now, if you place your POCOs beyond the service boundary, the ModifiedUserName must also be sent by your WCF service consumer along with the rest of the data. If this LastModifiedUser property is stored in another property and you want to update LastModifiedUser from that property, you may either expose this property to them for updates or edit the second code as follows:


foreach (ObjectStateEntry entry in objectStateEntries) {

    ReadOnlyCollection fieldsMetaData = entry.CurrentValues
            .DataRecordInfo.FieldMetadata;

    FieldMetadata sourceField = fieldsMetaData
            .Where(f => f.FieldType.Name == "YourPropertyName").FirstOrDefault();             

    FieldMetadata modifiedField = fieldsMetaData
        .Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();

    if (modifiedField.FieldType != null) {

        string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
        if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
            entry.CurrentValues.SetString(modifiedField.Ordinal,
                    entry.CurrentValues[sourceField.Ordinal].ToString());
        }
    }
}


Hope this was useful.

25
9/28/2010 3:29:37 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow