In the previous articles of this series we saw how to initialize the environment to use NHibernate.Envers and how to query it for getting information about the revisions it creates.
It’s now time to focus on the methods that allows us to actually retrieve the different revisions details (such as the state of the whole entity at a given revision and similar) and the history of the modifications an object has been subject to.
Once again our operation entry point is the IAuditReader object, but this time we will use its CreateQuery() method.
CreateQuery() returns an AuditQueryCreator object that exposes all the functions we need to perform search queries against the database; AuditQueryCreator provides a series of normal methods that specifies the entity we want to look for using a typeof(T) argument as their first parameter. It also exposes the generic version of those methods and we’ll focus our attention on those (because I hate to write down typeof() everywhere, if I can avoid it), the non generic functions are however very similar and their usage should be clear anyway.
Each of these generic functions will return an IEntityAuditQuery<T> object, these kind of objects work exactly like the usual NHibernate ICriteria API: we can add search criteria to filter out the results, order the results, perform pagination, add projections and so on.
To get the results from an IEntityAuditQuery object we can use two methods:
.Single() - it will return a single instance of the class representing the result, or an exception if more than one result is available.
.Results() - it will return an enumeration of the class representing the result.
Actually we have tree different methods we can use to build up queries against our revision database tables, let’s see them in detail:
The query built using this method will ultimately return to us an IEnumerable<T> (or a single instance of T, or the result of a projection), the enumeration will contains all the instances of the specified type of entity at the requested revision; one or more filtering conditions can be added:
[Test]
public void ForEntitiesAtRevision()
{
using (ISession s = _nh.SessionFactory.OpenSession())
{
// look for a non existing revision
using (var tx = s.BeginTransaction())
{
IAuditReader auditReader = s.Auditer();
var rev = auditReader.CreateQuery().ForEntitiesAtRevision<Person>(1).Results();
Assert.IsNotNull(rev);
Assert.That(rev.Count() == 2);
// print the data on the console
Utils.PrintEntityList(rev);
tx.Commit();
}
}
}
A couple of days ago Matteo Migliore asked me a question about why String.Equals(string, StringComparison) was not supported in Linq to NHibernate while it was in Entity Framework.
With those things in mind we have all the tools to cover the gaps and implement what we need; basically we want to ‘force’ a case insensitive string comparison at database level using something like this:
var res = Session.Query().Where(t => t.T1.Equals("test", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
I’m not a true SQL expert, but when it comes to string comparison a lot of database stuff kicks in (like collations and such) and every database can have a different default behavior to compare strings (by default it should be case insensitive, but I’m not really sure of this...so DBA experts can say their own here); aside of that, I think that the default behavior ORMs have is to rely on what the database does.
Matteo confirmed this last statement looking at the SQL expression that Entity Framework 4 generated for his test domain when calling Equals passing in an xxxIgnoreCase StringComparison, in both case (with and without the parameter) EF4 generated this SQL statement:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Customers] AS [Extent1]
WHERE N'temp' = [Extent1].[FirstName]
As you can see this is totally dependent on the database and it can ignore our will to perform a case insensitive equals (due to the database collation for example)...so it’s not a ‘perfect’ translation of what the user wanted to do and it may introduce subtle bugs in your code.
Linq to NHibernate raises us a ‘warning’ saying that this kind of operation is not supported (intentionally or not...we’ll never know...), so the first step is to extend the provider to support this kinds of equals:
public class EqualsStringGenerator : BaseHqlGeneratorForMethod
{
public EqualsStringGenerator()
{
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition(x => x.Equals(null, StringComparison.CurrentCulture))
};
}
To query for our revision entities we need to use the IAuditReader interface, we have two ways to obtain it:
Using the AuditReaderFactory.Get(ISession) method.
Using the ISession.Auditer() extension method.
Once we have the object in place we can start querying for data. There isn’t many documentation around (here’s the only link I was able to find and it’s from the original Java project: Hibernate Envers - Easy Entity Auditing); so your best bet is to build the documentation file from the source project or to download the source code itself and look at the comments there.
As you saw in the previous article NHibernate.Envers approaches the entity version problem defining a unique master table to hold a global revision number to which attach the history of the entities that are part of a single revision.
It creates a REVINFO table and maps it over a DefaultRevisionEntity class.
In this article we’ll focus on getting information about the Revision object, this is the class that actually identifies the current revision number and revision timestamp (it can be customized as we’ll see in a next article of this series).
Let’s see each function in detail (I’ll just look at the generic version, because I hate to write typeof() everywhere):
T GetCurrentRevision<T>(bool persist)
“Gets an instance of the current revision entity, to which any entries in the audit tables will be bound. Please note the if persist is false, and no audited entities are modified in this session, then the obtained revision entity instance won't be persisted. If persist is true, the revision entity instance will always be persisted, regardless of whether audited entities are changed or not”.
This very simple test (assuming we have an empty database) will give you an idea of how it work:
// assuming an empty database
using (ISession s = _nh.SessionFactory.OpenSession())
{
using (var tx = s.BeginTransaction())
{
IAuditReader auditReader = s.Auditer();
DefaultRevisionEntity o = auditReader.GetCurrentRevision<DefaultRevisionEntity>(false);
tx.Commit();
Assert.IsNotNull(o);
// it's transient
Assert.AreEqual(0, o.Id);
}
using (var tx = s.BeginTransaction())
{
IAuditReader auditReader = s.Auditer();
DefaultRevisionEntity o = auditReader.GetCurrentRevision<DefaultRevisionEntity>(true);
tx.Commit();
Assert.IsNotNull(o);
// it's persisted
Assert.AreEqual(1, o.Id);
}
}
Looking at the returned class you can have information about the Revision to which your (current or next) changes will be attached to.
T FindRevision<T>(long revision)
Gets the RevisionEntity at the given revision number, if the revision doesn’t exist a ‘RevisionDoesNotExistException’ will be raised.
The following tests will shot its usage:
[Test]
public void FindRevision_DoesNotExists()
{
// assuming an empty database
Assert.Throws<RevisionDoesNotExistException>(() =>
{
using (ISession s = _nh.SessionFactory.OpenSession())
{
// look for a non existing revision
using (var tx = s.BeginTransaction())
{
IAuditReader auditReader = s.Auditer();
var rev = auditReader.FindRevision<DefaultRevisionEntity>(1);
Assert.IsNotNull(rev);
tx.Commit();
}
}
});
}
Recently I had the need to introduce a sort of ‘Entity Versioning’ in a portion of my applications, the whole data access strategy was realized using NHibernate + XML / ConfORM mappings.
I started the thing introducing an poor auditing system using a listener and the pre-insert and pre-update events. This will allow me to track who created a record and the last one that modified it (and when) in an easy way (you can find some examples on this technique in a lot of blog posts around, just look for NHibernate + auditing in any web search engine).
The afore mentioned strategy is enough for the great part of my domain, but then I had to move a step forward and not only log who make the action, but also I needed to store the state of the entity at the time it was modified (being it updated or deleted), in short I need the whole history of the changes on that entity. My first thought was to extend my original approach and save the entity in another version of the previous listener...huge work to do!
Then I reminded Fabio Maulo talking about the release of NHibernate.Envers at one of the last UgiAlt workshops I attended to...Gotcha! Let’s take a look at it and see if it fits my needs.
Download the whole source code at https://bitbucket.org/RogerKratz/nhibernate.envers, you need to if you use the latest bits of NHibernate 3.2 because you will need to update the referenced libraries and recompile the project.
Let’s start considering this very simple domain:
public class Person : Entity<int>
{
protected Person()
{
}
I don’t remind where I originally got this Tip, but it’s proven to be very useful to save lots of time when you have very big solution and your projects are failing to build.
I am reporting it here as a note to myself because I really missed it when I had to install fresh copies of Visual Studio on new machines.
It’s basically a simple piece of macro you can add to the Visual Studio ‘EnvironmentEvents’ module:
Go to Tools -> Macro -> Macros IDE...
Look for MyMacros \ EnvironmentEvents in the project explorer tree.
Add this piece of code inside the module:
' A project failed to compile, so cancel the build and beep
Private Sub BuildEvents_OnBuildProjConfigDone(ByVal Project As String, ByVal ProjectConfig As String, ByVal Platform As String, ByVal SolutionConfig As String, ByVal Success As Boolean) Handles BuildEvents.OnBuildProjConfigDone
If Success = False Then
DTE.ExecuteCommand("Build.Cancel")
Beep()
Threading.Thread.Sleep(250)
Beep()
Threading.Thread.Sleep(250)
Beep()
Threading.Thread.Sleep(250)
Beep()
End If
End Sub
Some days ago the .Net User Group I belong to (DotNetMarche, for those who do not know) organized an event that was part of the “Microsoft & Community Tour 2011”. We had a multi-track workshop event that was entirely devoted to the web development using some of the major CMS application frameworks we have around today.
In the main track we had the occasion to talk about one the lasted addition to the landscape: Orchard.
Due to work issues Ugo Lattanzi, that was scheduled to host a session about developing a module in Orchard couldn’t be with us, so Michele Aponte and I step up and covered his session too.
The result was a good workshop with a nice attendance that seemed to be very interested by the potentiality of this ‘new’ application framework.
Here are the slides of my “1 session and a half”, the event was in Italian language and the same is for the slides (sorry for my English readers):
Sadly I don’ have any photo about the “Fish based” Community Dinner we had after the event...it was funny as usual with a lot of crazy guys (looking at the face of the waiter when he asked us if we wanted to end the dinner with a coffee and we ordered another first dish instead was priceless!).