views:

147

answers:

4

I have an entity model that has audit information on every table (50+ tables)

 CreateDate
 CreateUser
 UpdateDate
 UpdateUser

Currently we are programatically updating audit information.

Ex:

  if(changed){
        entity.UpdatedOn = DateTime.Now;
        entity.UpdatedBy = Environment.UserName;
        context.SaveChanges();
   }

But I am looking for a more automated solution. During save changes, if an entity is created/updated I would like to automatically update these fields before sending them to the database for storage.

Any suggestion on how i could do this? I would prefer to not do any reflection, so using a text template is not out of the question.

A solution has been proposed to override SaveChanges and do it there, but in order to achieve this i would either have to use reflection (in which I don't want to do ) or derive a base class. Assuming i go down this route how would I achieve this?

For example

EXAMPLE_DB_TABLE
 CODE
 NAME
 --Audit Tables
 CREATE_DATE
 CREATE_USER
 UPDATE_DATE
 UPDATE_USER

And if i create a base class

 public abstract class IUpdatable{

     public virtual DateTime CreateDate {set;}
     public virtual string CreateUser { set;}
     public virtual DateTime UpdateDate { set;}
     public virtual string UpdateUser { set;}
 }

The end goal is to be able to do something like...

   public overrride void SaveChanges(){
        //Go through state manager and update audit infromation
        //FOREACH changed entity in state manager
        if(entity is IUpdatable){
           //If state is created... update create audit.
           //if state is updated... update update audit
        }
   }

But I am not sure how I go about generating the code that would extend the interface.

A: 

Yes, I think you can do something like this. The general idea would be

  1. Handle ObjectContext.SavingChanges.
  2. In that event, call ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified)
  3. Iterate the results and set properties if needed.
Craig Stuntz
"Iterate the results and set properties if needed." scares me... you suggesting reflection?
Nix
No, I'm not. Go through each modified entity and look at its type. If it's one you want to audit, set `entity.UpdatedOn`, etc.
Craig Stuntz
Which unless they all extend some interface you would have to do reflection ?
Nix
I would pick an interface. But no, it's not the only choice. You can test the type and then cast, too.
Craig Stuntz
How would I make all entities extend the base class (which would contain the audit fields)? In all honestly i don't know how to map this in the edmx..
Nix
Like I said, I'd pick an interface. You don't change the EDMX. You add interface support in a partial class you create.
Craig Stuntz
A: 

Look to PostSharp. Very nice solution for such tasks.

Aen Sidhe
any chance you could give me a little more... i am guessing the idea is i tag my properties with trace attributes>?
Nix
Yes. Sorry, didn't see your question.There is a possibility to define one assembly-wide Trace attirubute.
Aen Sidhe
A: 

Is it not simpler to 'stamp' the updated rows via database triggers? Are the information provided in the database not adequate enough? (database user vs. os user)

oli
we can't get the right user, we pull it from the calling context, or we would use triggers.
Nix
+1  A: 

Here was the final solution.

I basically check my model for the presence of 4 properties. If the entity contains

InsertedOn and InsertedBy and UpdatedOn and UpdatedBy I generate (using a tt file) the following entity that implements an IAuditable interface.

public interface IAuditable
{
    void SetInsertedOn(DateTime date);
    void SetInsertedBy(string user);
    void SetUpdatedOn(DateTime date);
    void SetUpdatedBy(string user);
}

public partial class ExampleDBtable: EntityObject, Audit.IAuditable
{
     //Normal EDMX stuff here..
     public static ExampleDBtable CreateExampleDBtable(int id, string code, string name, DateTime insertedOn, string insertedBy, DateTime updatedOn, string updatedBy)
       {

       }
       //Extension points.
    #region IAuditable Members

    public void SetInsertedOn(DateTime date)
    {
        this._InsertedOn = date;
    }

    public void SetInsertedBy(string user)
    {
        this._InsertedBy = user;
    }

    public void SetUpdatedOn(DateTime date)
    {
        this._UpdatedOn = date;
    }

    public void SetUpdatedBy(string user)
    {
        this._UpdatedBy = user;
    }

    #endregion

    }

I also generate code to handle the updating of audit information

   public ExampleDBObjectContext(string connectionString) : base(connectionString, "publicExampleDBObjectContext")
    {
        this.SavingChanges += new EventHandler(UpdateAuditInformation);
        this.OnContextCreated();
     }

And then finally I implemented the UpdateAuditInformation

   foreach (ObjectStateEntry entry in
            context.ObjectStateManager.GetObjectStateEntries(
            EntityState.Added | EntityState.Modified))
        {
              //Start pseudo code..
              if(entry.Entity is IAuditable){
                 IAuditable temp = entry.Entity as IAuditable;
                 temp.SetInsertedOn(DateTime.Now);
                 temp.SetInsertedBy(Env.GetUser());
                 ...

              }
        }

I opted not to show the .tt file because it looks horrible in the editor.

Nix