A common business requirement in application development is auditing changes to stored entities. Which entities were added, modified, or deleted? Who made the change? Which properties were modified?
In this recipe, we'll use NHibernate Envers, which provides exactly that kind of auditing functionality.
Complete the Eg.Core
model from Chapter 2, Models and Mappings.
Eg.Envers
.Eg.Core
model and mappings from Chapter 2, Models and Mappings to this new project.Eg.Envers
.Install-Package NHibernate.Envers
Product
class, add the following attributes:public class Product : Entity { public virtual string Name { get; set; } public virtual string Description { get; set; } [Audited] public virtual Decimal UnitPrice { get; set; } }
Eg.Envers.Runner
.Eg.Envers.Runner
project using NuGet Package Manager Console, by running the following commands:Install-Package NHibernate Install-Package NHibernate.Envers Install-Package log4net
App.config
with the standard log4net
and hibernate-configuration
sections, just as we did in the Configuring NHibernate with App.config and Configuring NHibernate logging recipes in Chapter 1, The Configuration and Schema.Progr
am.cs
, use the following code:class Program { static void Main() { XmlConfigurator.Configure(); var cfg = new Configuration().Configure(); cfg.IntegrateWithEnvers( new AttributeConfiguration()); var sessionFactory = cfg.BuildSessionFactory(); var schemaExport = new SchemaExport(cfg); schemaExport.Execute(true, true, false); using (var session = sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { session.Delete("from Product"); tx.Commit(); } Guid productId; using (var session = sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { productId = (Guid) session.Save(new Product { Name = "A product", Description = "Some product we sell", UnitPrice = 143.73M }); tx.Commit(); } using (var session = sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { var product = session.Get<Product>(productId); product.UnitPrice = 900M; tx.Commit(); } using (var session = sessionFactory.OpenSession()) using (session.BeginTransaction()) { var reader = AuditReaderFactory.Get(session); var oldProduct = reader.Find<Product>(productId, 1); Console.WriteLine( "Product price at revision 1 is " + oldProduct.UnitPrice); } } }
Product price at revision 1 is 143,73000
.NHibernate Envers implements event listeners to intercept changes to entities. The changes are recorded in tables that Envers creates for each audited entity. These tables can be used to retrieve and query historical data without much effort.
Similar to source control systems, Envers has the concept of revision. One transaction is one revision (unless the transaction did not modify any audited entities). As revisions are global and have a revision number, you can query for various entities of that revision, retrieving a (partial) view of the database at that revision. You can find a revision number having a date, and the other way round, you can get the date at which a revision was committed.
In this recipe, we configured Envers using the attribute configuration. First, we mark the properties that we want to audit with the [Audit]
attribute. Then we tell NHibernate that we want to integrate with Envers. If we want to audit all properties in an entity, you can mark the whole class with the [Audit]
attribute, and if you need to exclude some properties from being audited you can exclude them by marking them with the [Exclude]
attribute.
At the cfg.IntegrateWithEnvers(new AttributeConfiguration());
line, Envers analyzes all mapped entities and creates additional metadata for auditing. It also registers the necessary event listeners.
We continue with saving an entity to the database. Later, we get this entity from a database and change the audited property UnitPrice
.
At the end, we create an instance of IAuditReader
. The IAuditReader
interface provides an interface to query the audit data stored in a database. This interface is very rich, and you can perform complex queries in addition to just retrieving old entity states.
NHibernate Envers also offers fluent configuration by code. To configure by code, just use the following code:
var enversConf = new FluentConfiguration(); enversConf.Audit<Product>() .Exclude(x => x.Name) .Exclude(x => x.Description); cfg.IntegrateWithEnvers(enversConf);
As you can see, when using FluentConfiguration
there is no ability to specify that we want to audit only one property in an Entity, so we need to exclude properties we do not want to audit.
More information about NHibernate Envers is available at https://envers.bitbucket.io.
18.117.231.15