nhibernatenhibernate-envers

Running into problem setting NHibernate Envers with table per class mapping


We have been looking at using Envers, but have run into a road block. We use a table/class structure that is using table per class inheritance for a couple of classes. While NHibernate and Envers are loading, an error occurs when it tries to create the tables for the subclasses.

NHibernate.MappingException: Unable to build the insert statement for class Demo.Vehicle_AUD: a failure occured when adding the discriminator ---> System.ArgumentException: The column 'VehicleTypeId' has already been added in this SQL builder

Parameter name: columnName

Here is an example similar to what we are using.

// Maps to Vehicle table
public Vehicle
{
  public int VehicleId {get;set;}
  pubic VehicleType VehicleTypeId {get;set;}
}

// Maps to Vehicle table with discriminator for VehicleTypeId == Car
public Car : Vehicle
{
   public decimal MaxSpeed {get;set;}

}

// Maps to Vehicle table with discriminator for VehicleType == Airplane
public Airplane : Vehicle
{
   public decimal MaxAirspeed {get;set;}
   public decimal MaxAltitude {get;set;}
}

Table definition:

VehicleId int identity primary key
VehicleTypeId int foreign key to VehicleTypeId on VehicleType table
MaxSpeed decimal null
MaxAirspeed decimal null
MaxAltitude decimal null

We are using FluentNHibernate:

var fluentConfig = FetchDbConfiguration(connectionString)
    .Mappings(mapper =>
        {
            mapper.FluentMappings.AddFromAssemblyOf<Vehicle>()
        })
    .ExposeConfiguration(cfg =>
        {
            var enversConf = new FluentConfiguration();
            //enversConf.Audit<Vehicle>();
            enversConf.Audit<Car>();
            enversConf.Audit<Airplane>();
            nhConf.IntegrateWithEnvers(enversConf);        
        });

var nhConfiguration = fluentConfig.BuildConfiguration();
return nhConfiguration;

Mappings:

    public partial class VehicleMap : ClassMap<Vehicle>
    {
        public VehicleMap()
        {
        Table("Vehicle");
        LazyLoad();
        Id(x => x.VehicleId)
                    .Column("VehicleId")
                    .CustomType("Int32")
                    .Access.Property()
                    .Not.Nullable()
                    .Precision(10)                
                    .GeneratedBy.Identity();
        DiscriminateSubClassesOnColumn("VehicleTypeId", 0)
            .CustomType<int>()
            .ReadOnly()
            .SqlType("int")
            .Not.Nullable();
        }   
    }
public partial class CarMap : SubclassMap<Car>
{
   public CarMap()
   {
       DiscriminatorValue(1); // 1 = Car
       Map(x => x.MaxSpeed)    
        .Column("MaxSpeed")
        .CustomType("Decimal")
        .Access.Property()
        .Generated.Never()
        .Default(@"0")
        .Precision(19)
        .Scale(4);
       }
    }

Airplane is mapped similarly to car using SubclassMap

The error seems to be happening because Envers is trying to create the Vehicle table for both of the subclasses. I have tried different variations of including/excluding the Vehicle class for auditing.

My first question is does Envers support the use table per class inheritance? If it does, can anyone point me to examples/documentation on how to configure it for table per class?

Thanks.


Solution

  • It turned out we were mapping our classes using an attribute of the discriminator that Envers didn't support yet. Our discriminator column is mapped to another object/table, rather than a base type. We submitted a small patch to add support for it.

    https://github.com/nhibernate/nhibernate-envers/commit/9ae9555ec5e4d4443d5d5ff18c97bf1685278b8e

    We were setting the insert attribute of the discriminator to false, so that NHibernate would know that the column is mapped to another class.

    https://nhibernate.info/doc/nh/en/index.html#mapping-declaration-discriminator

    insert (optional - defaults to true): set this to false if your discriminator column is also part of a mapped composite identifier.

    The Envers generated audit table mappings weren't including this setting, so when NHibernate loaded the generated class mappings, it tried to create the property for the base class and the subclasses. This is what caused the problem we were experiencing. The patch we submitted, looks for this type of mapping and then adds the insert="false" attribute to the discriminator element.