A couple months ago I wrote a basic set of extension methods to handle automatic auditing in LINQ to SQL. Well I have received a large number of emails regarding this particular project so I have decided to focus on cleaning up my v2 API and releasing it on CodePlex. There was a lot of room for improvement from version 1 and today I am going to post the all new LINQ Audit Trail code. This new version is significantly enhanced in the previous version.
Objective
Automatic auditing of all inserts/updates/deletes for any table in your database with a single line of code, including:
- What table was modified?
- What fields changed?
- Who made the change?
- When did it occur?
Usage
Simply define your audit definitions at any time before calling SubmitChanges();
1
2
3
4
| this .Products.Audit(); this .Categories.Audit(); this .Orders.Audit().AuditAssociation(o => o.Order_Details); this .Contacts.Audit().AuditAssociation(c => c.Addresses).AuditAssociation(c => c.PhoneNumbers); |
Updates and Download
Please see the CodePlex project for updates and new releases. DoddleAudit
New Features and Fixes in V2
- A significantly refined API for defining which tables to audit, including automatic primary key lookups.
- Inserted records will have their primary keys correctly stored in the audit table now.
- Built-in support for auditing across relationships.
- E.g., assume you want to audit a Contacts table which has a 1-to-many relationship to the Addresses table, thus allowing any number of Addresses to each Contact. Well ideally you want to show these Address audits on your ContactDetails.aspx, which is exactly what you can see in the screenshot below.
- E.g., assume you want to audit a Contacts table which has a 1-to-many relationship to the Addresses table, thus allowing any number of Addresses to each Contact. Well ideally you want to show these Address audits on your ContactDetails.aspx, which is exactly what you can see in the screenshot below.
- For example, notice in the above screenshot that “Address Type” has a value of “1” since that is how the data is stored in the table. This number does little good for the end user, so in V2 it is now possible to customize specific properties as needed. I will explain more details about this soon. Below I am overriding the default auditing of the Product.CategoryID property by querying the category by looking up the CategoryID, returning the Category.CategoryName, and renaming the audit field to “Category” since “CategoryID” is no longer accurate.
1
2
3
4
5
6
7
8
| public class ProductAuditResolver : AuditPropertyResolver<product> { protected override void CustomizeProperties() { CustomizeProperty(p => p.CategoryID, categoryId => GetCategoryByID(cid).CategoryName, "Category" ); } } </product> |
Instructions
- Download the latest release from CodePlex, the source code and compiled DLL are available, and add reference to Doddle.Linq.Audit.dll in your project that contains your LINQ to SQL DBML
- At a minimum you will need to add 2 tables to your database (and your DBML) to store the audit records. Please see the schema below and add these tables to your database.
- Open your DBML and click in the designer surface. In the property pane you will need to change the Base Class property of your generated DataContext to beDoddle.Linq.Audit.LinqToSql.AuditableDataContext
- Lastly you will need to create a partial DataContext class to wire up the auditing infrastructure to match your database schema. Add a new Class file to your project and insert the following code. Customize if necessary to match your auditing schema.123456789101112131415161718192021222324252627282930313233
public
partial
class
NorthwindEntitiesDataContext
{
protected
override
void
InsertAuditRecordToDatabase(EntityAuditRecord record)
{
AuditRecord audit =
new
AuditRecord();
audit.Action = (
byte
)record.Action;
audit.AuditDate = DateTime.Now;
audit.AssociationTable = record.AssociationTable;
audit.AssociationTableKey = record.AssociationTableKey;
audit.EntityTable = record.EntityTable;
audit.EntityTableKey = record.EntityTableKey;
audit.UserName = HttpContext.Current.User.Identity.Name;
foreach
(ModifiedEntityProperty av
in
record.ModifiedProperties)
{
AuditRecordModifiedField field =
new
AuditRecordModifiedField();
field.MemberName = av.MemberName;
field.OldValue = av.OldValue;
field.NewValue = av.NewValue;
audit.AuditRecordModifiedFields.Add(field);
}
this
.AuditRecords.InsertOnSubmit(audit);
}
protected
override
void
DefaultAuditDefinitions()
{
this
.Products.Audit();
this
.Categories.Audit().AuditAssociation(c => c.Products);
}
}