views:

571

answers:

7

I've been asked to create a simple DataGrid-style application to edit a single table of a database, and that's easy enough. But part of the request is to create an audit trail of changes made, who made them, and the date/time.

How might you solve this kind of thing?

(I'll be using C# in VS2008, ADO.NET connected to SQL Server 2005, WPF and Xceed's DataGrid, if it makes any difference.)

A: 

I'd log them in another table. Just structure the table to include the information you want.

John at CashCommons
+1  A: 

The best way to do this is set up triggers in the database that write to audit tables.

HLGEM
Depending on how you want the auditing to be used, all tables being audited could write to a single table. This allows a tick in the box for 'yes, everything is audited', without overcomplicating things. If you actually need to read it, then not so good...
Paddy
ONe audit table ,very bad idea. You will get locking issues on inserts with many users as everything will go to that table.
HLGEM
+1  A: 

There are two common ways of creating audit trails.

  1. Code your data access layer.
  2. In the database itself using triggers.

There are advantages and disadvantages to both. Some people prefer one over the other. It's often down to the type of app and the type of database use you can expect.

If you do it in your DA layer it's pretty much up to you. You just need to add code to every method that saves to the database to also save a log of the changes. This auditing code could be in your DA layer code, or even in your stored procs in your database if you are using stored procs for everything. Essentially the premise is the same, any time you make a change to the database, log that change.

If you want to go down the triggers route, you can write custom triggers for each table, or fashion a more generic trigger that works the same on lots of tables. Check out this article on audit triggers. This works by firing of triggers whenever a change is made, and the triggers log the changes. Remember that if you want to audit SELECT statements, you can't use triggers, you'll have to do that with in code/stored proc auditing. It's also worth remember that depending on your database, triggers may not fire in all circumstances. For example, most databases don't fire triggers during TRUNCATE statements. Check that your triggers get fired in any case that you need auditing.

Alternately, you could also take a look at using the service broker to do async auditing on a dedicated machine. This is more complex and takes a bit of configuring to set up.

Which ever way you do it you need to decide on the format the audit log will take. Normally you would save this log in your database, but you could just save it in a log file or whatever suits your requirements. You could use a single audit table that logs all changes, or you could have an audit table per main table being audited. For large scale implementations you could even consider putting the audit tables in a totally separate database. If your logging into a table, it's common to have a "change type" field which indicates if the audited change was an insert, update or delete style of change, along with the changed data, user who made the change and the date/time the change was made. Don't forget to include the old and new data for update style changes.

Simon P Stevens
Audit code in application programs/store procedures is a bad idea: a different program/interactive user that access that table can potentially bypass auditing.
MaD70
ewall
NEver do aufditing inteh data acces layer, that is just asking for trouble. There are other ways to change the data that need to be recorded. This is something that MUST be done in the database if you wanta full audit trail.
HLGEM
mmm.. the design detailed in that article is an Entity-Attribute-Value model (EAV) (http://en.wikipedia.org/wiki/Entity-attribute-value_model). Suggested reading: http://tonyandrews.blogspot.com/2004/10/otlt-and-eav-two-big-design-mistakes.html and http://www.simple-talk.com/opinion/opinion-pieces/bad-carma/
MaD70
I think what's important is to apply these techniques with consideration. Neither are particularly suitable to very large scale high load databases. Auditing in data access code can work in some circumstances, but you have to consider the cons, which as MaD70 says is that it's possible to bypass the auditing. Like wise, with a generic style trigger, you can have problems processing the data if the scheme changes as the audit trail may longer map to the scheme. There is no perfect way, but you just need to carefully consider each, there are several good answers here.
Simon P Stevens
Thanks for the links, MaD70 -- good reading!As I mentioned in an earlier comment, my needs here are very simple--I'm just keeping a record of a few fields that only change a few times a year. I don't expect ever to need the audit table for anything more than a more-or-less human-readable list.I tried another trigger which generated a separate audit table for each table I wanted the history for, and included both the old and the new values, but it was more than necessary... so I went back to the single-table EAV-ish design.
ewall
Oh, and this is kind of amusing -- the database (and related application) in question, which was designed by someone else a couple years ago, makes heavy use of a very poorly designed "One True Lookup Table". Ugh!
ewall
+2  A: 

Most universal method would be to create another table for storing versions of record from the first table. Then, you can remove all the data from main table. Suppose you need versioning of a table Person(PersonId, Name, Surname):

CREATE TABLE Person 
(
   PersonId INT,                   // PK
   CurrentPersonVersion INT        // FK
);

CREATE TABLE PersonVersion
(
  PersonVersionId INT,             // PK
  PersonID                         // FK 
  Name VARCHAR,                    // actual data
  Surname VARCHAR,                 // actual data

  ChangeDate                       // logging data
  ChangeAuthor                     // logging data
)

Now any change requires inserting new PersonVersion and updating the CurrentPersonVersionID.

PanJanek
+1  A: 

The other way of doing this apart from triggers is this, 1) Have five columns, UpdFlag, DelFlag, EffectiveDate and TerminatedDate for each table you want to do an audit trail on. 2) code your sproc's in such a way that when you do an update, to pass in the all of the row's column data into the sproc, update the row by setting the TerminatedDate to the date that was updated, and mark the UpdFlag and to put in the datetime into the column 3) Then create a new row with the new data (which is really updated). and put in a new date now for the EffectiveDate and the TerminatedDate set to the max date.

Likewise if you want to do a deletion of the row, simply update the row by marking the DelFlag as set, the TerminatedDate with the datetime now. You are in effect doing a soft delete and not an actual sql's Delete.

In that way, when you want to audit the data, and to show a trail of the changes, you can simply filter the rows for those that have the UpdFlag set, or between EffectiveDate and TerminatedDate. Likewise for those that were deleted, you filter for those that have the DelFlag set or between EffectiveDate and TerminatedDate. For the current rows, filter the rows that have both flags set off. The advantage is you don't have to create another table for the audit when the trigger is used!

Hope this makes sense, Best regards, Tom.

tommieb75
My bad! That should have read 4 columns, not five. Sorry! :(
tommieb75
@Tommieb75: You know you can edit it right =;)
Simon P Stevens
I repeat myself: what if a different program/an interactive user bypass such store procedures? With triggers and appropriate access control you can be (quite, depending on DBMS) sure that each change is audited.
MaD70
@MaD70: That's true, but the security is enforced by going down the proper route and enforces the usage of such application to do so. Bypassing sprocs which imho that would be classified as a security breach - like why should it be bypassed which defeats the point of an audit? Thanks for your input and it has given me food for thought! ;) Take care!
tommieb75
Tommie, if I'm doing an update of all the records for a particular client I'm not going to use some stored proc the application uses and run it ina loop 100,000 times. Nor will I use it for data imports nor will people trying to commit fraud or just trying to destroy data becasue they are mad.
HLGEM
@tommieb75: you are welcome. In real life the risk that no one will remember (or is left) to enforce such **implicit** policy is high, so better to put it in the DB and let the DBMS enforce it (appropriate accompanying design documentation is also recommended).
MaD70
A: 

I'd go triggers route, by creating table with similar structure to updated one, with additional columns for tracking changes like ModifiedAt etc. And then adding on update trigger that will insert changes to that table. I find it easier to maintain than have everything in the application code. Ofcourse many people tend to forget about triggers when it comes to questions like 'wtf this table is changing' ;) Cheers.

pablox
+2  A: 

Ditto use triggers.

Anyone considering soft deletion should have a read of Richard Dingwall's The trouble with soft delete.

martin clayton