NHibernate.Mapping.Attributes
NHibernate.Mapping.Attributes copied to clipboard
Mapping issues with `SubclassAttribute`
I have found some issues when using SubclassAttribute
with attribute mapping, for example, the base class is:
[Class(Schema = "public", Table="base_resources")]
[Discriminator(Column = "type")]
public class BaseResource {
[Id(Name = nameof(Id), Column = "id", Type = "long", Generator = "trigger-identity")]
public virtual long Id { get; set; }
[Property(Name=nameof(Name), Column = "name", Type = "string", Length = 32, NotNull = true)]
public virtual string Name { get; set; }
[Property(Name=nameof(Type), Column = "type", Type = "string", Length = 64, NotNull = true, Insert = false, Update = false)]
public virtual string Type { get; set; }
}
The generated xml mapping is correct :
<class table="base_resources" schema="public" name="NHibernate.Extensions.UnitTest.TestDb.BaseResource, NHibernate.Extensions.UnitTest">
<id name="Id" column="id" type="long" generator="trigger-identity" />
<discriminator column="type" />
<property name="Name" type="string" column="name" length="32" not-null="true" />
<property name="Type" type="string" column="type" length="64" not-null="true" update="false" insert="false" />
</class>
And I have a sub class which is:
[Subclass(DiscriminatorValue = "data_api", ExtendsType = typeof(BaseResource), Lazy = true)]
public class DataApi : BaseResource {
[Join(Schema = "public", Table = "data_apis", Fetch = JoinFetch.Select)]
[Key(Column = "id")]
[Property(Name = nameof(Statement), Column = "statement", Length = 128, NotNull = true)]
public virtual string Statement { get; set; }
[Property(Name = nameof(Parameters), Column = "parameters", Length = 128, NotNull = true)]
public virtual string Parameters { get; set; }
}
This looks correct, but the xml mapping generated is not correct:
<subclass discriminator-value="data_api" extends="NHibernate.Extensions.UnitTest.TestDb.BaseResource, NHibernate.Extensions.UnitTest" lazy="true" name="NHibernate.Extensions.UnitTest.TestDb.DataApi, NHibernate.Extensions.UnitTest">
<property name="Parameters" column="parameters" length="128" not-null="true" />
<join table="data_apis" schema="public" fetch="select">
<key column="id" />
<property name="Statement" column="statement" length="128" not-null="true" />
</join>
</subclass>
The
property
element forParameters
goes out of thejoin
element, which should be inside thejoin
element.
Then I change the code for DataApi
, move all of the PropertyAttribute
to one property, like this:
[Subclass(DiscriminatorValue = "data_apis", ExtendsType = typeof(BaseResource), Lazy = true)]
public class DataApi : BaseResource {
[Join(Schema = "public", Table = "data_apis", Fetch = JoinFetch.Select)]
[Key(Column = "id")]
[Property(Name = nameof(Statement), Column = "statement", Length = 128, NotNull = true)]
[Property(Name = nameof(Parameters), Column = "parameters", Length = 128, NotNull = true)]
public virtual string Statement { get; set; }
public virtual string Parameters { get; set; }
}
Then the xml mapping generated is corrected now, looks like:
<subclass discriminator-value="data_api" extends="NHibernate.Extensions.UnitTest.TestDb.BaseResource, NHibernate.Extensions.UnitTest" lazy="true" name="NHibernate.Extensions.UnitTest.TestDb.DataApi, NHibernate.Extensions.UnitTest">
<join table="data_apis" schema="public" fetch="select">
<key column="id" />
<property name="Parameters" column="parameters" length="128" not-null="true" />
<property name="Statement" column="statement" length="128" not-null="true" />
</join>
</subclass>
But is the sub class DataApi
has a many-to-one
mapping like this:
[Subclass(DiscriminatorValue = "data_api", ExtendsType = typeof(BaseResource), Lazy = true)]
public class DataApi : BaseResource {
[Join(Schema = "public", Table = "data_apis", Fetch = JoinFetch.Select)]
[Key(Column = "id")]
[ManyToOne(Name = "DataSource", Column = "data_source_id", ClassType = typeof(DataSource), NotFound = NotFoundMode.Ignore, Lazy = Laziness.Proxy, Fetch = FetchMode.Select)]
[Property(Name = nameof(Statement), Column = "statement", Length = 128, NotNull = true)]
[Property(Name = nameof(Parameters), Column = "parameters", Length = 128, NotNull = true)]
public virtual DataSource DataSource { get; set; }
public virtual string Statement { get; set; }
public virtual string Parameters { get; set; }
}
The xml mapping generated is not correct again, like this:
<subclass discriminator-value="data_api" extends="NHibernate.Extensions.UnitTest.TestDb.BaseResource, NHibernate.Extensions.UnitTest" lazy="true" name="NHibernate.Extensions.UnitTest.TestDb.DataApi, NHibernate.Extensions.UnitTest">
<join table="data_apis" schema="public" fetch="select">
<key column="id" />
<many-to-one name="DataSource" class="Beginor.GisHub.DataServices.Data.DataSource, Beginor.GisHub.DataServices" column="data_source_id" fetch="select" lazy="proxy" not-found="ignore" />
</join>
</subclass>
There is only one many-to-one
element , other properties is not generated.
But if I move the ManyToOne
after Property
, like this:
[Subclass(DiscriminatorValue = "data_api", ExtendsType = typeof(BaseResource), Lazy = true)]
public class DataApi : BaseResource {
public virtual DataSource DataSource { get; set; }
[Join(Schema = "public", Table = "data_apis", Fetch = JoinFetch.Select)]
[Key(Column = "id")]
[Property(Name = nameof(Statement), Column = "statement", Length = 128, NotNull = true)]
[Property(Name = nameof(Parameters), Column = "parameters", Length = 128, NotNull = true)]
[ManyToOne(Name = "DataSource", Column = "data_source_id", ClassType = typeof(DataSource), NotFound = NotFoundMode.Ignore, Lazy = Laziness.Proxy, Fetch = FetchMode.Select)]
public virtual string Statement { get; set; }
public virtual string Parameters { get; set; }
}
Then the xml mapping generated is correct again, like this:
<subclass discriminator-value="data_api" extends="NHibernate.Extensions.UnitTest.TestDb.BaseResource, NHibernate.Extensions.UnitTest" lazy="true" name="NHibernate.Extensions.UnitTest.TestDb.DataApi, NHibernate.Extensions.UnitTest">
<join table="data_apis" schema="public" fetch="select">
<key column="id" />
<property name="Parameters" column="parameters" length="128" not-null="true" />
<property name="Statement" column="statement" length="128" not-null="true" />
<many-to-one name="DataSource" class="Beginor.GisHub.DataServices.Data.DataSource, Beginor.GisHub.DataServices" column="data_source_id" fetch="select" lazy="proxy" not-found="ignore" />
</join>
</subclass>
I think that's very strange behavior,is there any who can tell me the magic?
And I think SubclassAttribute
should be used the same way as ClassAttribute
, like this:
[Subclass(DiscriminatorValue = "data_api", ExtendsType = typeof(BaseResource), Lazy = true)]
[Join(Schema = "public", Table = "data_apis", Fetch = JoinFetch.Select)]
[Key(Column = "id")]
public class DataApi : BaseResource {
[ManyToOne(Name = "DataSource", Column = "data_source_id", ClassType = typeof(DataSource), NotFound = NotFoundMode.Ignore, Lazy = Laziness.Proxy, Fetch = FetchMode.Select)]
public virtual DataSource DataSource { get; set; }
[Property(Name = nameof(Statement), Column = "statement", Length = 128, NotNull = true)]
public virtual string Statement { get; set; }
[Property(Name = nameof(Parameters), Column = "parameters", Length = 128, NotNull = true)]
public virtual string Parameters { get; set; }
}
Is anyone who can improve it?
Ideally, you should use JoinSubClass
instead. That is joined-subclass
in hbm mapping. Join
is not about sub-classes, but about mapping a part of an entity properties to another table. (Like join
hbm mapping.) So it cannot be applied on the whole class, it is not meant for this.
But maybe you want to use this hybrid mapping, which requires to use join
.
Join
should still allow to group many properties in the same join, but I do not really know how we are supposed to do that with this library.
From the code samples in the project, it seems the correct way is this (notice the additional ordering):
[Subclass(DiscriminatorValue = "data_api", ExtendsType = typeof(BaseResource), Lazy = true)]
public class DataApi : BaseResource {
[Join(0, Schema = "public", Table = "data_apis", Fetch = JoinFetch.Select)]
[Key(1, Column = "id")]
[Property(2, Name = nameof(Statement), Column = "statement", Length = 128, NotNull = true)]
[Property(3, Name = nameof(Parameters), Column = "parameters", Length = 128, NotNull = true)]
[ManyToOne(4, Name = "DataSource", Column = "data_source_id", ClassType = typeof(DataSource), NotFound = NotFoundMode.Ignore, Lazy = Laziness.Proxy, Fetch = FetchMode.Select)]
public virtual string Statement { get; set; }
public virtual string Parameters { get; set; }
public virtual DataSource DataSource { get; set; }
}
As stated in the documentation:
As long as there is no ambiguity, you can decorate a member with many unrelated attributes. A good example is to put class-related attributes (like
<discriminator>
) on the identifier member. But don't forget that the order matters (the<discriminator>
must be after the<id>
). The order to use comes from the order of elements in the NHibernate mapping schema. Personally, I prefer using negative numbers for these attributes (if they come first!).
That is the "magic". Unless using a component for grouping the properties of the join, or a [RawXml] mapping of the join, I do not think there is any other way to group many properties in the same join. (I do not think duplicating the Join
attribute on each property would work.)
Adding order is better, but it's not obligatory, and the ManyToOne
must be at the end.
Thanks for the detailed explaination, I know the JoinSubClass
for interitance mapping, and have used in my projects.
But this time I just want to try hybrid mapping with Subclass
and Join
, because I will have a few types of resource, and there is a type
property on base_resource
which indicate the resource type, so I think maybe it's better to use hybrid mapping, and then I found the strange behavior of Join
attribute.
Anyway, both joined-subclass
and hybrid mapping
can work, and thanks for showing me the magic
by your detailed explaination.