Meanwhile, I was able to change the behavior, which schema will be used by
NHibernate.Envers to audit collection entities.
Sadly, it was not possible to do this by using the supported hook-in
capabilities included in Envers, so I had to create my own fork :-(
For those, who had similar problems and are interested in a solution, see
my changes below.
Please notice, I've used a typecheck for CustomType to detect the
"problematic" collection elements (this is OK for my purposes, but probably
not in general).
I still believe, Envers would do better, to move map elements from
key-properties to properties in general (like I do it now for my
CustomTypes). Including dictionary values in a primary key is an
unnecessary weakness in my opinion - even it is a workaround to prevent
primary key conflicts during Update (adding the REVTYPE to key-prorties
instead is a much more cleaner solution, I think).
/* Move collection element from key-property to property */
Configuration\Metadata\CollectionMetadataGenerator.cs,
addValueToMiddleTable(..):
var mapped = _mainGenerator.BasicMetadataGenerator.AddBasic(xmlMapping,
new
PropertyAuditingData(prefix, "field"),
value,
null, true, true);
==>
var isMapValue = prefix == "element" && value.Type is CustomType;
var xmlParent = isMapValue ? xmlMapping.Parent : xmlMapping;
var mapped = _mainGenerator.BasicMetadataGenerator.AddBasic(xmlParent,
new
PropertyAuditingData(prefix, "field"),
value,
null, true, !isMapValue);
/* This forces the REVTYPE to move from property to key-property (prevents
primary key conflicts during update/delete) */
Configuration\Metadata\CollectionMetadataGenerator.cs,
isEmbeddableElementType(..):
return _propertyValue.Element.Type is ComponentType;
==>
return _propertyValue.Element.Type is ComponentType ||
_propertyValue.Element.Type is CustomType;
/* Add the element value to the data part instead using the idData part
(otherwise it would been inserted as null) */
entities\mapper\relation\mapcollectionmapper.cs, MapToMapFromObject(...)
var keyValue = (KeyValuePair<TKey, TValue>) changed;
ElementComponentData.ComponentMapper.MapToMapFromObject(session,
idData, data, keyValue.Value);
IndexComponentData.ComponentMapper.MapToMapFromObject(session, idData,
data, keyValue.Key);
==>
var keyValue = (KeyValuePair<TKey, TValue>) changed;
ElementComponentData.ComponentMapper.MapToMapFromObject(session,
idData, data, keyValue.Value);
IndexComponentData.ComponentMapper.MapToMapFromObject(session, idData,
data, keyValue.Key);
data.Add("element", keyValue.Value);
Am Dienstag, 13. Oktober 2015 13:17:47 UTC+2 schrieb Matthias Kientz:
>
> To be honest, I'm not so much familiar with the internal structure of
> NHibernate and NHibernate.Envers, but may be we we are talking past each
> other :-(
>
> What I do specify, is the definition of the entities (via fluent) and some
> "magic" inside NHibernate (or included tools) does create the data base
> schema automatically (we use SchemaExport). This works fine since years.
>
> To create and add the audit tables, I do nothing else as include Envers
> and call the Audit()-function for specific entities.I understand, envers
> does not create any db statements directly, but somewhere inside there must
> be knowledge about which "audit-structure" is necessary to audit the
> entities, I have specified. This is done automatically and does well for
> all entities based on a simple entity object. But for entities, mapped from
> a Dictionary collection, i still have the problems, described above with
> the audit table :-(
>
>
> Am Freitag, 9. Oktober 2015 15:38:56 UTC+2 schrieb Roger:
>>
>> << But for the moment, I still have the problem to find a place, where I
>> am able to hook my workaround into NHibernate/Envers. I know, envers use
>> the infrastructure of NHibernate to create the table on the database, but
>> there must be a place where envers creates automatically a kind of schema
>> definition for the audit table (depending of the configuration and the
>> entity definition to audit, right?). If you have some tipps for me, where
>> this is implemented (or better: how to hook in into this behavior), would
>> be very nice...>>
>>
>>
>>
>> No, envers doesn’t create any db schema implicitly. Nor does NHibernate.
>> There is some explicit code written by you (or some 3rd party lib) to
>> generate the schema, most probably you use SchemaExport somewhere.
>> SchemaExport uses NH’s configuration object’s mapping to create the db
>> schema.
>>
>> Right after that code has been run, you can of course modify the schema
>> whatever way you like.
>>
>>
>>
>> If you want explicit schema changes added to what’s normally produced by
>> SchemaExport, you can use <database-object> in your mapping.
>>
>>
>>
>> /Roger
>>
>>
>>
>> *From:* [email protected] [mailto:[email protected]] *On
>> Behalf Of *Matthias Kientz
>> *Sent:* den 9 oktober 2015 11:08
>> *To:* nhusers <[email protected]>
>> *Subject:* Re: [nhusers] Envers: erroneous primary key for mapped
>> collection
>>
>>
>>
>> Hi Roger,
>>
>> I had tested the workaround just to eliminate the value column from the
>> audit table primary key, but this leads to primary key constrain exception
>> when a collection entry is modified.
>> The reason for this is, that envers will add an entry with REVTYPE = del
>> for the old value (but with the new REV number), when i modify a collection
>> entry.
>> And afterwards envers will add another entry to the audit table with
>> REVTYPE = add.
>>
>> So, instead of just removing the value column from the audit table
>> primary key, I also added the REVTYPE to the primary key.
>> This works for all of my use cases.
>>
>> For me, it looks like, the problem with the deleted entries (described
>> above) was solved in envers by adding the value column to the PK, but
>> adding REVTYPE would do the same and is less problematically due to
>> database type restrictions. From my point of view, it would be an
>> improvement for envers, to do the same.
>>
>> But for the moment, I still have the problem to find a place, where I am
>> able to hook my workaround into NHibernate/Envers. I know, envers use the
>> infrastructure of NHibernate to create the table on the database, but there
>> must be a place where envers creates automatically a kind of schema
>> definition for the audit table (depending of the configuration and the
>> entity definition to audit, right?). If you have some tipps for me, where
>> this is implemented (or better: how to hook in into this behavior), would
>> be very nice...
>> Thanks a lot ;-)
>>
>>
>>
>> Here is an example, what is happening on the database (I've trunkated all
>> other rows, which are not involved):
>>
>>
>> After initial create:
>>
>> table: HardwareSetting
>> HardwareSetting_id | SettingValue | SettingName
>>
>> ***********************************************************************************
>> 2c98afee-4c34-4d44-b515-a52c00ac6eed | 500 | SettingKey1
>>
>> table: HardwareSetting_AUD
>> REV | HardwareSetting_id | SettingValue | SettingName |
>> REVTYPE
>>
>> ***********************************************************************************
>> 7 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 500 | SettingKey1 |
>> 0
>>
>>
>>
>>
>> After update the value of SettingKey1 from 500 to 600:
>>
>> table: HardwareSetting
>> HardwareSetting_id | SettingValue | SettingName
>>
>> ***********************************************************************************
>> 2c98afee-4c34-4d44-b515-a52c00ac6eed | 600 | SettingKey1
>>
>> table: HardwareSetting_AUD
>> REV | HardwareSetting_id | SettingValue | SettingName |
>> REVTYPE
>>
>> ***********************************************************************************
>> 7 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 500 | SettingKey1 |
>> 0
>> 8 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 500 | SettingKey1 |
>> 2
>> 8 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 600 | SettingKey1 |
>> 0
>>
>>
>>
>> After delete SettingKey1 from collection:
>>
>> table: HardwareSetting
>> HardwareSetting_id | SettingValue | SettingName
>>
>> ***********************************************************************************
>>
>>
>> table: HardwareSetting_AUD
>> REV | HardwareSetting_id | SettingValue | SettingName |
>> REVTYPE
>>
>> ***********************************************************************************
>> 7 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 500 | SettingKey1 |
>> 0
>> 8 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 500 | SettingKey1 |
>> 2
>> 8 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 600 | SettingKey1 |
>> 0
>> 9 | 2c98afee-4c34-4d44-b515-a52c00ac6eed | 600 | SettingKey1 |
>> 2
>>
>>
>>
>> Am Donnerstag, 8. Oktober 2015 18:04:16 UTC+2 schrieb Roger:
>>
>> With the mapping you have, the ”SettingValue” is supposed to be part in
>> the audited table’s primary key. Or rather – part of the generated audited
>> composite-id mapping for ”HardwareSettings”.
>>
>>
>>
>> << Has someone an idea where to hook in to change the way Envers creates
>> the schema for the audit tables?>
>>
>>
>>
>> Envers doesn't create any schema for you. I guess you use NH Core's
>> schemaexport to generate the schema for you? That tool will generate schema
>> for your NH configuration object (which also contains mapping for envers
>> audited entities).
>>
>> I understand this will lead to problem if this pk contains a
>> “StringClobUserType” mapping. If you manually drop/change your pk after it
>> has been generated by “X” (=schemaexport), will things work as expected for
>> you then?
>>
>>
>>
>>
>>
>>
>>
>> *From:* [email protected] [mailto:[email protected]] *On
>> Behalf Of *Matthias Kientz
>> *Sent:* den 8 oktober 2015 15:56
>> *To:* nhusers <[email protected]>
>> *Subject:* [nhusers] Envers: erroneous primary key for mapped collection
>>
>>
>>
>> I use NHibernate with Fluent and Envers.
>> I have an auditable class Hardware, which contains the property Settings,
>> which is mapped to a collection entity.
>>
>> Error:
>> The created audit table adds also the value column (SettingValue) to the
>> primary key, which fails in this case (SqlCeException: Long value data type
>> cannot be indexed.)
>>
>> Expected:
>> The value column SettingValue should not be part of the primary key.
>>
>>
>> */* definition of Setting (in class Hardware) */*
>> public virtual IDictionary<string, string> Settings
>> {
>> get { return _settings; }
>> set { _settings = value; }
>> }
>>
>> */* automapping override with fluent */*
>> mapping.HasMany(x => x.Settings)
>> .Not.LazyLoad()
>> .AsMap<string>("SettingName")
>> .KeyColumns.Add("HardwareSettings_id")
>> .Table("HardwareSettings")
>> .Element("SettingValue", x => x.Type<StringClobUserType>());
>>
>> */* trace of the resulting Hardware mapping (containing map
>> HardwareSettings) */*
>> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
>> <class xmlns="urn:nhibernate-mapping-2.2"
>> name="MyApp.Data.Model.Hardware.Hardware, MyApp.Data.Model,
>> Version=1.0.0.0, Culture=neutral, PublicKeyToken=866d4a0fa0599fe0"
>> table="`Hardware`">
>> <cache usage="read-write" />
>> <id name="Id" type="System.Guid, mscorlib, Version=4.0.0.0,
>> Culture=neutral, PublicKeyToken=b77a5c561934e089">
>> <column name="Id" />
>> <generator class="guid.comb" />
>> </id>
>> <map cascade="save-update" lazy="false" name="Settings"
>> table="HardwareSettings">
>> <cache usage="read-write" />
>> <key>
>> <column name="HardwareSettings_id" />
>> </key>
>> <index type="System.String, mscorlib, Version=4.0.0.0,
>> Culture=neutral, PublicKeyToken=b77a5c561934e089">
>> <column name="SettingName" />
>> </index>
>> <element
>> type="MyApp.DataAccess.NHibernate.UserTypes.StringClobUserType,
>> MyApp.DataAccess.NHibernate, Version=1.0.0.0, Culture=neutral,
>> PublicKeyToken=866d4a0fa0599fe0">
>> <column name="SettingValue" />
>> </element>
>> </map>
>>
>> <!-- several other properties and subclasses -->
>>
>> </class>
>> </hibernate-mapping>
>>
>> */* trace of the resulting HardwareSettings autition table mapping */*
>> <hibernate-mapping assembly="NHibernate.Envers" auto-import="false"
>> xmlns="urn:nhibernate-mapping-2.2">
>> <class entity-name="HardwareSettings_AUD" table="HardwareSettings_AUD">
>> <composite-id name="originalId">
>> <key-many-to-one class="NHibernate.Envers.DefaultRevisionEntity,
>> NHibernate.Envers, Version=1.0.0.0, Culture=neutral,
>> PublicKeyToken=e2c5b946037fb7f8" name="REV">
>> <column name="REV" />
>> </key-many-to-one>
>> <key-property name="Hardware_Id" type="Guid">
>> <column name="HardwareSettings_id" />
>> </key-property>
>> <key-property name="element"
>> type="MyApp.DataAccess.NHibernate.UserTypes.StringClobUserType,
>> MyApp.DataAccess.NHibernate, Version=1.0.0.0, Culture=neutral,
>> PublicKeyToken=866d4a0fa0599fe0">
>> <column name="SettingValue" />
>> <type
>> name="MyApp.DataAccess.NHibernate.UserTypes.StringClobUserType,
>> MyApp.DataAccess.NHibernate, Version=1.0.0.0, Culture=neutral,
>> PublicKeyToken=866d4a0fa0599fe0" />
>> </key-property>
>> <key-property name="mapkey" type="String">
>> <column name="SettingName" />
>> </key-property>
>> </composite-id>
>> <property insert="true" update="false" name="REVTYPE"
>> type="NHibernate.Envers.Entities.RevisionTypeType, NHibernate.Envers,
>> Version=1.0.0.0, Culture=neutral, PublicKeyToken=e2c5b946037fb7f8"
>> not-null="true" />
>> </class>
>> </hibernate-mapping>
>>
>>
>> */* created HardwareSettings table */*
>> create table HardwareSettings (
>> HardwareSettings_id UNIQUEIDENTIFIER not null,
>> SettingValue NTEXT null,
>> SettingName NVARCHAR(255) not null,
>> primary key (HardwareSettings_id, SettingName)
>> )
>>
>> */* created HardwareSettings audition table (with erroneous primary key)
>> */*
>> create table HardwareSettings_AUD (
>> REV INT not null,
>> HardwareSettings_id UNIQUEIDENTIFIER not null,
>> SettingValue NTEXT not null,
>> SettingName NVARCHAR(255) not null,
>> REVTYPE TINYINT not null,
>> primary key (REV, HardwareSettings_id, SettingValue, SettingName)
>> )
>>
>>
>> If this cannot be solved by myself or via configuration:
>> Has someone an idea where to hook in to change the way Envers creates the
>> schema for the audit tables?
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "nhusers" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to [email protected].
>> To post to this group, send email to [email protected].
>> Visit this group at http://groups.google.com/group/nhusers.
>> For more options, visit https://groups.google.com/d/optout.
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "nhusers" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to [email protected].
>> To post to this group, send email to [email protected].
>> Visit this group at http://groups.google.com/group/nhusers.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
--
You received this message because you are subscribed to the Google Groups
"nhusers" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/nhusers.
For more options, visit https://groups.google.com/d/optout.