Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping issues with SubclassAttribute #14

Open
beginor opened this issue Apr 26, 2022 · 2 comments
Open

Mapping issues with SubclassAttribute #14

beginor opened this issue Apr 26, 2022 · 2 comments

Comments

@beginor
Copy link
Member

beginor commented Apr 26, 2022

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 for Parameters goes out of the join element, which should be inside the join 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?

@beginor beginor changed the title Mapping issues with NHibernate.Mapping.Attributes.SubclassAttribute Mapping issues with SubclassAttribute Apr 26, 2022
@fredericDelaporte
Copy link
Member

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.)

@beginor
Copy link
Member Author

beginor commented Apr 27, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants