-->
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.  [ 9 posts ] 
Author Message
 Post subject: Can't persist collection(bag) of inherited classes / objects
PostPosted: Wed Apr 07, 2010 5:06 pm 
Newbie

Joined: Wed Apr 07, 2010 4:25 pm
Posts: 5
Hi,

I'm having problems with collections of inherited classes in hibernate.

Let's say I have a superclass named Content, and subclasses for two different types of content; Image and Article.

I also have a class named Document, which contains a collection of Content.

My problem is that when I add a new Content object to the collection my Document object, and then save the Document object using hibernate, the database will not have the new row in the table corresponding to the Content subclass. I must be making some mistake.


Database design is as follows:

Code:
DOCUMENT
id
name

DOCUMENT_CONTENT
id
document_id

IMAGE
id (same as DOCUMENT_CONTENT.id)
url

ARTICLE
id (same as DOCUMENT_CONTENT.id)
text

As you can see, the DOCUMENT_CONTENT.id is the same as IMAGE.id and ARTICLE.id. In other words, some sample data can look like:

Code:
DOCUMENT
id   name
1    A sample document

DOCUMENT_CONTENT
id   document_id
4    1
5    1

IMAGE
id   url
4    C:/images/an_image.jpg

ARTICLE
id   text
5    This is an article...

I don't know if this is the best database design.

If I insert these rows by hand, using a query analyzer, and use hibernate to get the Document with id 1, I will get an object with a collection of the correct types; one Image and one Article. Fine and dandy. However, if I would add for example a new Article instance to a Document object and save it, the DOCUMENT_CONTENT table gets updated correctly, but no new rows gets created in the ARTICLE table.

The collection in the Document class is of type IList. Other than that, the code implementation of the classes is not important; they are just entites with virtual properties (corresponding to the database design) and nothing more.

I think there's something wrong with my mapping. I'm using table-per-subclass mapping. See how it looks today below (note that the Image and Article classes does not have a mapping. When I tried adding mapping for them, I got an error).

Code:
<!-- MAPPING FOR Document CLASS -->

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="Project.Models.Entities" assembly="Project">

  <class name="Document" table="DOCUMENT">

    <id name="ID">
      <column name="id" sql-type="int" not-null="true"/>
    </id>

    <property name="Name">
      <column name="title" length="256" not-null="true" />
    </property>

    <bag name="ContentCollection" table="DOCUMENT_CONTENT" lazy="false">
      <key column="document_id"></key>
      <many-to-many class="Content" column="id"></many-to-many>
    </bag>

  </class>

</hibernate-mapping>


<!-- MAPPING FOR Content CLASS -->

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="Project.Models.Entities" assembly="Project">
  <class name="Content" table="DOCUMENT_CONTENT">

    <id name="ID">
      <column name="id" sql-type="int" not-null="true"/>
    </id>

    <joined-subclass name="Image" table="IMAGE">
      <key column="id"/>
      <property name="Url" column="url"/>
    </joined-subclass>

    <joined-subclass name="Article" table="ARTICLE">
      <key column="id"/>
      <property name="Text" column="text"/>
    </joined-subclass>

  </class>
</hibernate-mapping>


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Thu Apr 08, 2010 9:59 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
http://nhforge.org/doc/nh/en/index.html#manipulatingdata-graphs

YOu have to specify a cascade option on the association:

<bag name="ContentCollection" table="DOCUMENT_CONTENT" lazy="false" cascade="all">
<key column="document_id"></key>
<many-to-many class="Content" column="id"></many-to-many>
</bag>

_________________
--Wolfgang


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Thu Apr 08, 2010 3:49 pm 
Newbie

Joined: Wed Apr 07, 2010 4:25 pm
Posts: 5
wolli,

thanks for taking the time to help me. I specified cascade="all" as you suggested, and it does create a row in the ARTICLE table, but it has ID 0, where I'd expect it to have the ID of the newly inserted row in the DOCUMENT_CONTENT table. And in that table I get two new rows, one where the document_id is null, and one where the id and document_id seem to be correct, but another column (not specified in my earlier example) is null where a value is expected.

This is what it looks like after I save a Document (note that I have an extra column in the DOCUMENT_CONTENT table which I omitted earlier for simplicity).

Code:
DOCUMENT
id   name
4    "A document"

DOCUMENT_CONTENT
id   document_id    name
1    null           "Some content"
2    4              null

ARTICLE
id    text
0     "Some article text"


And this is what I want:

Code:
DOCUMENT
id   name
4    "A document"

DOCUMENT_CONTENT
id   document_id    name
1    4              "Some content"

ARTICLE
id    text
1     "Some article text"


Something to note here is that I get the exact same result if I try to explicitly (or manually) save the article, after having saved the document.

What did I miss this time?


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Fri Apr 09, 2010 3:17 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
On a first glance, I think the id generator is missing in your mapping:

http://nhforge.org/doc/nh/en/index.html#mapping-declaration-id

I don't know if there's a default. If there's I suppose its "assigned". The you probably have some errors in your id assignment.

_________________
--Wolfgang


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Sun Apr 11, 2010 3:52 pm 
Newbie

Joined: Wed Apr 07, 2010 4:25 pm
Posts: 5
I tried the generator tag, which means I now have the following mapping for DOCUMENT_CONTENT:

Code:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="Project.Models.Entities" assembly="Project">
  <class name="Content" table="DOCUMENT_CONTENT">

    <id name="ID">
      <column name="id" sql-type="int" not-null="true"/>
      <generator class="native" />
    </id>

    <joined-subclass name="Image" table="IMAGE">
      <key column="id"/>
      <property name="Url" column="url"/>
    </joined-subclass>

    <joined-subclass name="Article" table="ARTICLE">
      <key column="id"/>
      <property name="Text" column="text"/>
    </joined-subclass>

  </class>
</hibernate-mapping>


However, after doing this I get an error when trying to persist the collection. The error is "Duplicate entry '2' for key 'PRIMARY'". I logged the SQL queries generated by NHibernate and they look very odd. It seems that NHibernate tries to insert two rows in the DOCUMENT_CONTENT table. This is in itself wrong of course, and the error it generates is because it tries to insert the two rows with the same ID.

Code:
Log row 1: INSERT INTO DOCUMENT_CONTENT values ( name )
Log row 2: SELECT LAST_INSERT_ID()
Log row 3: INSERT INTO ARTICLE (text, id) VALUES (?p0, ?p1);?p0 = 'sometext', ?p1 = 2
Log row 4: INSERT INTO DOCUMENT_CONTENT (document_id, id) VALUES (?p0, ?p1);?p0 = 27, ?p1 = 2

What I want to happen is:

1) A row gets inserted in DOCUMENT_CONTENT with auto-incremented ID and correct reference to document (document_id).
2) the last inserted ID from DOCUMENT_CONTENT is used as ID for a new inserted row in the ARTICLE table.

(This basically means log row 4 from above, followed by log row 2 and log row 3)

Even if I remove the column "name" from DOCUMENT_CONTENT, I get the same error, since NHibernate then tries to insert a row without column data: INSERT INTO DOCUMENT_CONTENT values ( ).

Sorry that I keep asking noob questions, but I'm really stuck here.


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Mon Apr 12, 2010 1:26 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
Can you post the code where you instantiate and save the objects ?

_________________
--Wolfgang


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Mon Apr 12, 2010 4:28 pm 
Newbie

Joined: Wed Apr 07, 2010 4:25 pm
Posts: 5
I played around a little bit more today and finally I got it to work... sort of.

This is the code I now use (C#):
Code:
DocumentService dsvc = new DocumentService();
Document doc = new Document();
doc.Name = "A test document";
Article article = new Article();
article.Name = "An article name";
article.Text = "An article text...";
article.Document = doc;       // this feels backwards, do I really need to do this?
doc.Contents.Add(article);
dsvc.SaveDocument(doc);

My problem was firstly that I had to make the relation between document and content(article) bi-directional, and that I had to explicitly set the document property of my content(article) object to the document in which it is contained (the commented line in the code above).

To me this feels a bit backwards. Is there some way to avoid having to do this? I guess I could pass the document to the constructor of Content, but I'd like to avoid that too.

My mappings now look like this:
Code:
<class name="Document" table="Document">

    <id name="ID">
      <column name="id" sql-type="int" not-null="true"/>
      <generator class="native"/>
    </id>

    <property name="Name">
      <column name="name" length="256" not-null="true" />
    </property>

    <bag name="Contents" table="document_content" lazy="false" cascade="all">
      <key column="document_id" not-null="true"></key>
      <one-to-many class="Content"></one-to-many>
    </bag>

  </class>

Code:
<class name="Content" table="document_content">

    <id name="ID">
      <column name="id" sql-type="int" not-null="true"/>
      <generator class="native"/>
    </id>

    <property name="Name">
      <column name="name" length="256" not-null="false" />
    </property>

    <many-to-one name="Document" class="Document" column="document_id" not-null="true"/>

    <joined-subclass name="Article" table="article">
      <key column="id"/>
      <property name="Text" column="text"/>
    </joined-subclass>

  </class>



Just in case, here are my Entities:

Document
Code:
public class Document
    {
        private int _id;
        private string _name;
        private IList<Content> _content;

        public virtual int ID
        {
            get { return _id; }
            set { _id = value; }
        }

        public virtual string Name
        {
            get { return _name; }
            set { _name = value; }
        }       

        public virtual IList<Content> Contents
        {
            get { return _content; }
            set { _content = value; }
        }

        public Document()
        {
            Contents = new List<Content>();
        }
    }


Content
Code:
    public class Content
    {
        private int _id;       
        private string _name;
        private Document _document;

        public virtual int ID
        {
            get { return _id; } set { _id = value; }
        }
       
        public virtual string Name
        {
            get { return _name; } set { _name = value; }
        }
       
        public virtual Document Document
        {
            get { return _document; } set { _document = value; }
        }
           
        public Content()
        {
        }
    }


Article
Code:
    public class Article : Content
    {
        private string _text;

        public virtual string Text
        {
            get { return _text; }
            set { _text = value; }
        }
    }


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Tue Apr 13, 2010 2:37 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
You need that if you model your association bi-directional. If you use a uni-directional association you won't. Have a look at here for the implications:

http://nhforge.org/doc/nh/en/index.html#example-parentchild

_________________
--Wolfgang


Top
 Profile  
 
 Post subject: Re: Can't persist collection(bag) of inherited classes / objects
PostPosted: Tue Apr 13, 2010 2:58 am 
Newbie

Joined: Wed Apr 07, 2010 4:25 pm
Posts: 5
Since I didn't get the behaviour I was looking for using the uni-directional approach, I will simply go for the bi-directional.

Thank you for your invaluable help, Wolfgang!


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 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.