Envers.Linq (and HQL+ full Criteria support): First prototype

142 views
Skip to first unread message

Peter Schojer

unread,
Sep 30, 2015, 7:17:11 AM9/30/15
to nhibernate-development
I recently started a project to extend Envers with common Nhibernate querying functionality like HQL, Criteria or linq.
The goal is to make querying between  history and current as transparent and easy as possible.
The current implementation is nowhere complete but with a few days of work I am already able to use HQL, Criteria and Linq to query Envers.
To make further progress and overcome some of the shortcomings I could need some help though :-)

Linq Query example:

var query = Session.Query<User>().Where(x => x.Name == User1Version2Name && x.Addresses.Count > 0).ToList(1);

For Linq I added a ToList(int versionNumber) extension method. Providing a versionNumber lower equal 0 executes queries against the current tables, otherwise AuditTables are queried.
Nice and simple. No need to write queries twice for Envers and Nhibernate.
Similar overloads exist for Criteria and HQL (QueryOver not done yet):

var query = Session.CreateCriteria<User>().Add(Restrictions.Eq("Name", "test")).List<User>(1);
var query = Session.CreateQuery("Select Id from User where Name = :name").SetParameter("name", User1Version2Name).List<int>(1);

How does it work:
The current implementation uses an interceptor to rewrite the generated sql from current tables to audit tables.
Biggest advantage:
- simple and easy to implement, no need to write own Linq provider
- makes use of Nhibernates Linq provider, benefits from any future work/bugfix done there

Biggest disadvantage:
- executed SQL and returned data types (object + proxies!) do not match the data types from the expression tree

Take this Query example: Session.Query<User>().Where(x => x.Name == "name2" && x.Addresses.Count > 0).ToList(1);

We build up an expression tree for table User. In reality we are returning an AuditUser object with Version=1 (Version is part of the key).
Those two types are unrelated (and also all proxy types created when evaluating a record).
I am currently searching for a way to fix that.

First idea, an expression tree visitor rewriting data types.
This type information could be parsed from Envers (?) but I donot know enough about Envers. Are types dynamically generated at startup?
Or does envers simple uses a generic type containing two properties: RevisionInfo Rev, TObject Obj.
That would make rewriting the query impossible.

Also dynamically changing the types after the SQL was generated is (I think) not possible for interceptors.
Or maybe somehow intercept proxy/object generation?

As a workaround, I have some ugly reflection hacks in place which guarantee that returned objects/created proxies are never cached in a session.
So all version queries behave as if they are executed on a stateless session (try to access a proxy object -> exception).
This at least prevents errors due to caching.

But I'd really prefer to get rid of those hacks :-)

SourceCode is attached. VS2013 project. Just let Nuget restore the packages, then adapt your connectionString in Setup/Globals.cs and see unit tests for example usage.

best regards,
Peter


NHibernate.Envers.Querying.zip

Roger Kratz

unread,
Oct 2, 2015, 10:44:26 AM10/2/15
to nhibernate-...@googlegroups.com

Nice work!

 

When querying audited entities, quite often both entity type _and_ revision type is involved. Revision number is mandatory on revision type and the way you add this to the query by adding an extension method is good enough if only querying for revision number. However, when having a custom revision type (which I guess most projects have) with, for example, ModifiedBy and ModifiedAt you often want to make queries based on these properties.

To get some type safe way to make queries, I think the revision type needs to be included in the query somehow.

 

<< 

This type information could be parsed from Envers (?) but I donot know enough about Envers. Are types dynamically generated at startup?

>> 

 

I’m not sure I understand but if you ask what CLR type audited entities are mapped to, it is simply an IDictionary. No new CLR types are created for audited entities (except for their collection proxies). When IntegrateWithEnvers is called on NH’s Configuration object, new mappings are added for each mapped entity. These mappings don’t have any class mapped to them (in hbm mapping, the class element doesn’t have any name attribute – only entity-name).

When envers query API is called, this data/dictionary is “transformed” into entity objects (and, possibly, a revision entity).

 

/Roger

--

---
You received this message because you are subscribed to the Google Groups "nhibernate-development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nhibernate-develo...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Peter Schojer

unread,
Oct 4, 2015, 3:06:19 AM10/4/15
to nhibernate-development
Hello Roger,
thanks for the compliment but I am still at a loss on how to solve the type problem I am facing.
I want to make the first level session cache work, so I can cache the same object with different versions in the same session(current, version 1,version 1000...)
I would somehow need a hook into NHibernate so that I can rewrite the query type information of the returned types to that IDictionary type.
That's one possbility (if someone has a better one, please tell me!)

Maybe one of the Nhibernate Devs has an idea?

Once this is done, I could tackle the other open issues:
- queryover
- additional Linq methods (like First or FirstOrDefault)
- ranged queries: ie give me all objects from version 1 to x (correctly handling joins, grouping, subqueries,...)

About supporting querying custom revision type fields: this can't be done with my extension methods and I think they should not. For this use-case, the whole purpose is to hide that revision info from the user and let one reuse the same query for history and for current. Once you start querying custom fields, you no longer care about the current tables, only about AuditTables. That's a different use-case requiring a different solution,maybe even with new generated CLR types. Once you have that types (and the "soft" foreign keys of that audit type point to other audit types), Linq support for that could be possible (still, those modeled FK needs to be resolved by subqueries/envers revision joins).

Anyway, back to my current problem. Anyone has an idea on how I can rewrite the query type information of the returned types to that IDictionary type?

Best regards,
Peter

Peter

To unsubscribe from this group and stop receiving emails from it, send an email to nhibernate-development+unsub...@googlegroups.com.

Peter Schojer

unread,
Dec 18, 2015, 3:28:53 AM12/18/15
to nhibernate-development
Newest Version:
- now rewrite SQL queries work properly
- bug with parameter resolving was fixed
- ValidityAuditStrategy is now supported (in terms of read performance way faster than defaultauditstrategy, simply try a query with at least three joins and take a look at the rewritten query).

Proxies are still not supported though, so always a use a converter/ResultTransformer.
To get started check the testsuite. Edit Globals.cs to use either default or validity strategy, and update your db connection.

best regards,
Peter
NHibernate.Envers.Querying20151218.zip

Roger Kratz

unread,
Dec 22, 2015, 8:47:12 AM12/22/15
to nhibernate-...@googlegroups.com

Hi,

 

Is it available on some public repo somewhere?

 

/Roger

 

From: nhibernate-...@googlegroups.com [mailto:nhibernate-...@googlegroups.com] On Behalf Of Peter Schojer


Sent: den 18 december 2015 09:29
To: nhibernate-development <nhibernate-...@googlegroups.com>

--

---
You received this message because you are subscribed to the Google Groups "nhibernate-development" group.

To unsubscribe from this group and stop receiving emails from it, send an email to nhibernate-develo...@googlegroups.com.

Peter Schojer

unread,
Dec 23, 2015, 1:40:08 AM12/23/15
to nhibernate-development
Currently not.
Don't really know where to put it (also have little experience with GIT (just checking out code), mostly svn and TFS).
The goal was to provide a fully working solution and then try to get it added to Envers :-).
But this would also require working proxies (currently proxies throw an exception - as with stateless nhibernate session), so it's not really an option yet.

Currently, committing it somewhere as an addon would probably be the best solution but this would require some changes for building (I use nant?), unit tests (not enough), code standards?

br
peter

To unsubscribe from this group and stop receiving emails from it, send an email to nhibernate-development+unsub...@googlegroups.com.

Reply all
Reply to author
Forward
0 new messages