-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 
Author Message
 Post subject: Table-per-Class Problem: Discriminator and Property
PostPosted: Tue Jan 22, 2008 10:38 am 
Newbie

Joined: Tue Aug 28, 2007 1:01 pm
Posts: 8
Location: Indonesia
Hi everybody,

I think I have a problem with table-per-class hierarchy mapping model.

Suppose there's a DocumentLine as a base class for ItemLine. I want that both of them are mapped to exactly 1 (one) table on the database. Table-per-class hierarchy mapping model is what I have in mind according to this inheritance type. I use DocumentLineType column as the discriminator column.

The problem occurs since I want the discriminator use the same column as the DocumentLineType property. I want it that way because I also have to maintain the DocumentLineType at the application (as a property), not just as an internal NHibernate mapping stuff (as a discriminator).

Is there any way I can achieve this?

CMIIW and thanks before.

Microsoft SQL Server 2005 SP2

NHibernate Version: 1.2.0 GA

Mapping Documents

<class name="Max.Finance.NHibernate.DocumentLine, Max.Finance.NHibernate" table="DocumentLine" dynamic-update="true" discriminator-value="-1">
<id name="Id" column="DocumentLineId" type="System.Int64" access="nosetter.camelcase">
<generator class="identity">
</generator>
</id>
<discriminator column="DocumentLineTypeId" type="System.Int64" not-null="true" />
<version name="RowVersion" column="RowVersion" type="System.Int64" access="nosetter.camelcase" />
<property name="RowGuid" column="RowGuid" type="System.Guid" access="nosetter.camelcase" not-null="true" insert="false" update="false" generated="insert" />
<property name="DocumentLineType" column="DocumentLineTypeId" type="Max.Finance.NHibernate.DocumentLineType, Max.Finance.NHibernate" access="property" not-null="true" insert="true" update="false" />
<many-to-one name="Document" column="DocumentId" class="Max.Finance.NHibernate.Document, Max.Finance.NHibernate" access="property" not-null="true" />
<property name="FinancialDateTime" column="FinancialDateTime" type="System.DateTime" access="property" not-null="true" insert="true" update="false" />
<property name="LineOrder" column="LineOrder" type="System.Int32" access="property" not-null="true" />
<property name="SubAmount" column="SubAmount" type="System.Decimal" access="nosetter.camelcase" not-null="true" />
<property name="Amount" column="Amount" type="System.Decimal" access="nosetter.camelcase" not-null="true" />
</class>

<subclass name="Max.Finance.NHibernate.ItemLine, Max.Finance.NHibernate" extends="Max.Finance.NHibernate.DocumentLine, Max.Finance.NHibernate" dynamic-update="true" discriminator-value="4">
<many-to-one name="Item" column="ItemAccountId" class="Max.Finance.NHibernate.Item, Max.Finance.NHibernate" access="property" not-null="true" />
<property name="UnitPrice" column="UnitPrice" type="System.Decimal" access="property" not-null="true" />
<property name="Quantity" column="Quantity" type="System.Double" access="property" not-null="true" />
<property name="DiscountAmount" column="DiscountAmount" type="System.Decimal" access="property" not-null="true" />
</subclass>


Exception and Stack Trace
NHibernate.ADOException was unhandled by user code
Message="could not insert: [Max.Finance.NHibernate.ItemLine][SQL: INSERT INTO DocumentLine (ItemAccountId, UnitPrice, Quantity, DiscountAmount, RowVersion, CreationDateTime, ModificationDateTime, DeletionDateTime, DocumentLineTypeId, DocumentId, FinancialDateTime, LineOrder, SubAmount, Amount, DocumentLineTypeId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 4)]"
Source="NHibernate"
StackTrace:
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object[] fields, Boolean[] notNull, SqlCommandInfo sql, Object obj, ISessionImplementor session)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object[] fields, Object obj, ISessionImplementor session)
at NHibernate.Impl.ScheduledIdentityInsertion.Execute()
at NHibernate.Impl.SessionImpl.Execute(IExecutable executable)
at NHibernate.Impl.SessionImpl.DoSave(Object theObj, EntityKey key, IEntityPersister persister, Boolean replicate, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.DoSave(Object obj, Object id, IEntityPersister persister, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.SaveWithGeneratedIdentifier(Object obj, CascadingAction action, Object anything)
at NHibernate.Impl.SessionImpl.Save(Object obj)
at NHibernate.Impl.SessionImpl.SaveOrUpdate(Object obj)
at Castle.Facilities.NHibernateIntegration.SessionDelegate.SaveOrUpdate(Object obj)
at Max.System.Data.NHibernate.EntityRepository`1.Save(T entity) in D:\Users\mahara\Projects\Personal\Software\Max.Finance\Private\1.0\Source Codes\Max.System.Data.NHibernate\EntityRepository.Generic.cs:line 382
at Max.System.Data.NHibernate.EntityValidation`1.Save(T entity) in D:\Users\mahara\Projects\Personal\Software\Max.Finance\Private\1.0\Source Codes\Max.System.Data.NHibernate\EntityValidation.Generic.cs:line 389
at Max.System.Data.NHibernate.EntityDataAccess`1.Save(T entity) in D:\Users\mahara\Projects\Personal\Software\Max.Finance\Private\1.0\Source Codes\Max.System.Data.NHibernate\EntityDataAccess.Generic.cs:line 304
at Max.Finance.NHibernate.SalesInvoiceDataManagement.Update(SalesInvoice salesInvoice) in D:\Users\mahara\Projects\Personal\Software\Max.Finance\Private\1.0\Source Codes\Max.Finance.NHibernate\Document\SalesInvoiceDataManagement.cs:line 121
at Max.Finance.NHibernate.SalesInvoiceManagement.ProcessForAddition(SalesInvoice salesInvoice) in D:\Users\mahara\Projects\Personal\Software\Max.Finance\Private\1.0\Source Codes\Max.Finance.NHibernate\Document\SalesInvoiceManagement.cs:line 30
at ISalesInvoiceManagementProxy1683386f56b148b2936d5dbf6a53991e.InvocationProcessForAddition_2.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Facilities.AutomaticTransactionManagement.TransactionInterceptor.Intercept(IInvocation invocation)

Inner Exception and Stack Trace
"Column name 'DocumentLineTypeId' appears more than once in the result column list."
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at NHibernate.Impl.BatcherImpl.ExecuteReader(IDbCommand cmd)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object[] fields, Boolean[] notNull, SqlCommandInfo sql, Object obj, ISessionImplementor session)

_________________
Regards,

Maximilian Haru Raditya


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 28, 2008 8:53 pm 
Regular
Regular

Joined: Wed Aug 15, 2007 7:37 am
Posts: 73
Did you get anywhere with this? I've just run into the same problem and it seems like it should be something really easy to fix. Setting insert="false" update="false" on the property declaration stops the error, but I don't know if it would have any weird side effects. It does seem like an oddity though. Maybe it's worth filing a JIRA report to add a 'name' attribute to discriminator declarations or something?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 29, 2008 12:08 am 
Newbie

Joined: Tue Aug 28, 2007 1:01 pm
Posts: 8
Location: Indonesia
SteveMc wrote:
Did you get anywhere with this? I've just run into the same problem and it seems like it should be something really easy to fix. Setting insert="false" update="false" on the property declaration stops the error, but I don't know if it would have any weird side effects. It does seem like an oddity though. Maybe it's worth filing a JIRA report to add a 'name' attribute to discriminator declarations or something?


The workaround (or the solution?) is (unfortunately back) to use table per subclass inheritance model. The only thing I worry with this model is the performance since you have joins involved, which I can understand so far as costly. The motivation to use table per class hierarchy is to have one table for those two types so I can avoid joining tables. CMIIW.

_________________
Regards,

Maximilian Haru Raditya


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 29, 2008 4:32 am 
Expert
Expert

Joined: Tue Aug 23, 2005 5:52 am
Posts: 335
Change the DocumentLineType property mapping to insert=false. The value is going to be automatically inserted for you by the discriminator mapping based on type of the instantiated object.

You're getting the exception because the property mapping adds the column name to the insert statement as does the discriminator mapping, leading to a duplicate column name in the SQL statement.

Cheers,

Symon.

_________________
Symon Rottem
http://blog.symbiotic-development.com


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 29, 2008 11:43 am 
Regular
Regular

Joined: Wed Aug 15, 2007 7:37 am
Posts: 73
Yeah, that worked for me - perhaps the original poster's problem isn't the same. Does setting update=false have any effect? Since the discriminator is automagically added to the INSERT I would presume it's also added to UPDATES (and in any case changing it could have bizarre behaviour). I do think this is probably something that needs documenting - I'll try and remember to file a report later.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Mar 01, 2008 11:56 pm 
Newbie

Joined: Tue Aug 28, 2007 1:01 pm
Posts: 8
Location: Indonesia
merge_s.rottem wrote:
You're getting the exception because the property mapping adds the column name to the insert statement as does the discriminator mapping, leading to a duplicate column name in the SQL statement.

Cheers,

Symon.


Yes, that's what actually bothers me.

Why there should be a property as a Discriminator and a property as a Property? Why don't they just be joined as a "Discriminator-Property"?

If I were going to propose such feature, would be there any problem with it? Or someone else perhaps has already reported this?

Moreover, actually I'm not sure how the original typical use of them when those features were designed for the first time. As far as I understand so far, it simply doesn't work for me :(.

Thanks!

_________________
Regards,

Maximilian Haru Raditya


Top
 Profile  
 
 Post subject:
PostPosted: Sun Mar 02, 2008 5:40 am 
Expert
Expert

Joined: Tue Aug 23, 2005 5:52 am
Posts: 335
I guess the idea of a discriminator is that essentially it's supposed to be invisible information that identifies which subclass an entity is in a class hierarchy. The discriminator is not a "property" since you have the class itself and can use that to determine it's type - the discriminator is just there to help NHibernate determine which object type to return.

That said, sometimes it *is* useful to have the discriminator available as a property, but the property should be read-only, no exceptions. This is because the discriminator is set based on the type of the entity and an entity type should *never* change. If you create a Cat object it shouldn't be possible to transform it into a Dog...let's keep that for the mad scientists.

So in essence I don't think there's value in creating a discriminator property. If you need to see it you simply have to map a property to the same database column as the discriminator but just make the property mapping update="false" insert="false" and the actual property in you class have a getter only.

Cheers,

Symon.

_________________
Symon Rottem
http://blog.symbiotic-development.com


Top
 Profile  
 
 Post subject:
PostPosted: Sun Mar 02, 2008 10:07 am 
Newbie

Joined: Tue Aug 28, 2007 1:01 pm
Posts: 8
Location: Indonesia
Alright, Symon, I got it.

I think I'm going to try your suggestion ASAP. I'll let you all know regarding the result.

Thanks!

_________________
Regards,

Maximilian Haru Raditya


Top
 Profile  
 
 Post subject:
PostPosted: Fri Mar 21, 2008 10:36 am 
Newbie

Joined: Tue Aug 28, 2007 1:01 pm
Posts: 8
Location: Indonesia
merge_s.rottem wrote:
Change the DocumentLineType property mapping to insert=false. The value is going to be automatically inserted for you by the discriminator mapping based on type of the instantiated object.


OK, now I can confirm it works for me.

Thanks Symon!

_________________
Regards,

Maximilian Haru Raditya


Top
 Profile  
 
 Post subject: Re: Table-per-Class Problem: Discriminator and Property
PostPosted: Mon Jun 07, 2010 7:35 am 
Newbie

Joined: Mon Jun 07, 2010 7:26 am
Posts: 5
I thought I was going to have to use the above approach of exposing the discriminator as a property to create a particular query.

However, it turns out that there is a neater solution (at least in Hibernate 3.2.6.ga):

Code:
crit.add(Restrictions.eq("class", "yourDiscriminatorNameFromTheHBM"));


Or to get a count of each type from a Criteria (as a List of Object arrays: offset 0 being the name of the discriminator and 1 being the count as an Integer):

Code:
crit.setProjection(Projections.projectionList()
      .add(Projections.groupProperty("class"))
      .add(Projections.count("class")));


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.