Better support for custom collections
Description
Environment
Activity
It was just an idea but...
My proposal was regarding FluentConfiguration... As an alternative to SetCollectionMapper (which sets a CustomCollectionMapperAttribute on one property), enable some possibility to say "whenever I use XxxCollectionType for a collection in NH Core I want to use XxxCollectionMapper for Envers" which simply adds CustomCollectionMapperAttributes on multiple properties.
...but it was just an idea. Any proposal that suits your needs are probably better .
So it would do something like adding a new if clause to CollectionMetadataGenerator.addMapper() to check for this global mapping? Right now, to get this to work, I'm using Reflection to set every audited collection property to use a templated CustomCollectionMapperFactory<TList, TItem> to set the mapping, and TList and TItem get passed down to the CollectionMapper and CollectionInitializer, which are otherwise almost identical to the basic List implementations of the same classes. TList is set to my CustomList<TItem> class. So I think that this might be a feasible approach.
Of course, at the moment we need to loop through our collection properties to check for any relations to non-audited classes anyway...
Thinking out loud here...
Would it help you if you could do something like (psuedo)
var fluent = new FluentConfiguration();
fluent.AddCollectionMapping(typeof(CustomBagType<IAuditChild>), typeof(MyEnversCustomBagType<IAuditChild>));
What I mean still relying on SetCollectionMapper but you can set it "globally". Every time Envers finds a custom collection type, it will use something user has provided.
<<I couldn't see why it shouldn't be possible to use a simple implementation of IList<T> in the same way as a regular list type.>>
In the domain model it's (probably) not declared as an IList<T> -> Envers cannot simply create a new list.
A simplified version of our data model includes the following parent class:
<class name="AuditParent" table="AuditParent" lazy="true">
<id name="Id" access="property" column="Id" type="Guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid.comb">
</generator>
</id>
<property name="Name" access="field.camelcase-underscore" type="String">
<column name="Name" />
</property>
<property name="Value" access="field.camelcase-underscore" type="String">
<column name="Value" />
</property>
<bag name="Children" table="AuditChild" lazy="true" inverse="true" optimistic-lock="false"
access="NHibernate.Envers.Tests.NetSpecific.UnitTests.CustomLists.InternalPropertyAccessor,NHibernate.Envers.Tests"
collection-type="CustomBagType`1[[IAuditChild]]">
<key column="AuditParentId" />
<one-to-many class="AuditChild" />
</bag>
</class>
I note that "inverse" is not set to true in the https://nhibernate.jira.com/browse/NHE-39#icft=NHE-39 test case, which is one reason why that one succeeds while this one fails.
Our custom NHibernate/ActiveRecord framework uses, among other things, Observable list classes for our collections, to allow for usage in WPF. I encountered difficulties when I first tried to use these with Envers, because the code assumed that anything using a CustomCollection has to do it in a certain way. I couldn't see why it shouldn't be possible to use a simple implementation of IList<T> in the same way as a regular list type.
Comments to https://nhibernate.jira.com/browse/NHE-39#icft=NHE-39 implied that this should be done on a collection-by-collection basis, and the "SetCollectionMapper" method treats it this way. We, however, are using our Observable classes model- and solution-wide, and over multiple solutions with generic code libraries in the backend, so this will result in mostly a tedious morass of reflection.