Nhibernate: How to represent Many-To-Many relationships with One-to-Many relationships? - nhibernate

I have read a post on the internet (I can no longer find that post for me to refeence) that a Many-To-Many relationship can be replaced with a one-to-many relationship. Can someone provide an example?

I just come up to that question, and realized, that there is missing any answer. And it is a shame, while I do often point out this NHibernate documentation statement: 24. Best Practices
Don't use exotic association mappings.
Good usecases for a real many-to-many associations are rare. Most of
the time you need additional information stored in the "link table".
In this case, it is much better to use two one-to-many associations to
an intermediate link class. In fact, we think that most associations
are one-to-many and many-to-one, you should be careful when using any
other association style and ask yourself if it is really neccessary.
Take a look at the example under the 23.2. Author/Work. Extract, the simplified version of the many-to-many relation between Author and Work:
<class name="Work" table="works" ...>
<id name="Id" column="id" generator="native" />
...
<set name="Authors" table="author_work" lazy="true">
<key>
<column name="work_id" not-null="true"/>
</key>
<many-to-many class="Author">
<column name="author_id" not-null="true"/>
</many-to-many>
</set>
</class>
And its many-to-many target Author:
<class name="Author" table="authors">
...
<set name="Works" table="author_work" inverse="true" lazy="true">
<key column="author_id"/>
<many-to-many class="Work" column="work_id"/>
</set>
</class>
So, if we would like to order the set of Works on load, we do have a problem. There is no column in the pair table. But what's more important, there is no way how to manage such a column.
What we can do, is to introduced the Pair object: AuthorWork and extend the Pair table as needed
public class AuthorWork
{
public virtual Author Author { get; set; }
public virtual Work Work { get; set; }
public virtual int OrderBy { get; set; }
}
Mapping of the AuthorWork
<class name="AuthorWork" table="author_work">
...
<many-to-one name="Author" column="author_id" />
<many-to-one name="Workr" column="work_id" />
<property name="OrderBy" />
Having this we can convert the many-to-many mapping to one-to-many, for example the Authors collection:
<set name="Authors" lazy="true"
order-by="OrderBy">
<key column="work_id" not-null="true"/>
<one-to-many class="AuthorWork" />
</set>
And we can manage the entity AuthorWork, set the OrderBy column, and therefore effectively work with the pairing table.
NOTE: have to agree with that suggestion in docsumentation The more requirements come, the more happy we are that we do have a way how to manage the relation!

Related

Deleting item with a many-to-many relation within it

I have a model contains a list of the same model as a many-to-many relationship, and I have a problem when deleting an item of this model type when having a relation with another item.
This error happened because of a foreign key error.
The model code is as the following:
public class Employee : ModelBase
{
public virtual string FullName { get; set; }
public virtual IList<Employee> Managers { get; set; }
}
The mapping file is as follows
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Models"
namespace="Models"
default-lazy="false">
<class name="Employee">
<id name ="Id">
<generator class="native" />
</id>
<property name="FullName"></property>
<bag name="Managers">
<key column="Id" />
<many-to-many class="Employee"/>
</bag>
</class>
</hibernate-mapping>
Suppose I have created two employees, emp1, emp2, and then I have added emp2 to Managers list in emp1.
If I want to delete emp1, I have the same error, but I can easily resolve it by clearing the managers list. but if I want to delete emp2, I cannot loop for every employee to see if the Managers list contains this employee!! it will reduce the performance.
My Question, is there an implicit way for nhibernate to handle this issue?
EDIT1 :
My delete statement is
var queryString = string.Format("delete {0} where Id = :id",
typeof(T));
session.CreateQuery(queryString)
.SetParameter("id", modelId)
.ExecuteUpdate();
Well, either we've got here the parent-child hierarchy, (x)or many-to-many relation.
I. In case of many-to-many, we are missing
the pairing table
the second, reverted view on this relationship.
The model we have is providing the up view: Managers, and should also provide the down view: Subordinates.
<bag name="Managers" table="ManagerSubOrdinates" >
<key column="ManagerId" />
<many-to-many class="Employee" column="SubordinateId"/>
</bag>
<bag name="Subordinates" table="ManagerSubOrdinates" inverse="true" >
<key column="SubordinateId" />
<many-to-many class="Employee" column="ManagerId"/>
</bag>
Now, we mapped both ends of the pairing table/relation ManagerSubOrdinates. If we will delete the Employee, the related records will be removed as well - but just from the pairing table. Not the related Managers or Subordinates
Finally the Deletion.
Now, when we do have the mapping in place, the correct way how to delete is like this
// load the Employee with ID == 1 into the ISession
var employee = session.Get<Employee>(1); // id 1
// pass this instance into the Delete
session.Delete(employee);
This will instruct NHibernate to properly remove all records from pairing table (both mappings) and then delete the Employee record itself
(not sure here) II. If this is in fact, the parent-child relation, we have to correct the mapping this way:
<bag name="Subordinates">
<key column="ManagerId" />
<many-to-many class="Employee"/>
</bag>
<many-to-one class="Employee" Name="Manager" column="ManagerId" >
Here I am just guessing (more Subordinates, one Manager), but The essence is in the column name ManagerId. In most scenarios, the key column does not have value "Id" which usually referes to the table Primary key, not the foreign key...

NHibernate and collection types

I am trying to develop my very first project with Nhibernate 3.0.
I've gone through loads of material (blogs, papers and samples) and I think I can understand the basics, pretty much.
I think I've understood the meaning of different types of collection but than when I see the examples found on the Internet I think I haven't really understood.
The documentation says that you should use a Set when you do not want duplicates and a List/Bag when you want to allow duplicates.
Most of the samples I have found are based on a typical situation where you have Orders/OrderLines.
If I have a look the mapping file of the Order I can see something like this:
<class name="OrderHeader"
table="Orders">
<id name="OrderId">
<generator class="hilo"/>
</id>
<property name="OrderDate"/>
<bag name="OrderItems" table="OrderDetails" cascade="all" inverse="true">
<key column="OrderId"/>
<one-to-many class="OrderDetail"/>
</bag>
</class>
<class name="OrderDetail"
table="OrderDetails">
<id name="DetailId">
<generator class="hilo"/>
</id>
<many-to-one name="ProductOrdered" column="ProductId"/>
<many-to-one name="Order" column="OrderId" />
</class>
I had expcted to see the OrderItems mapped as a Set; an order will have unique OrderItems?
Am I right?
At the same time I would expect to find a mapping for the class Product a bag of OrderItems
...
<bag lazy="true" name="OrderItems">
<key foreign-key="FK_OrderItems_Products">
<column name="ProductCode" />
</key>
.....
</bag>
...
In this situation a product would have a list of non-unique OrderItems.
Is there anything am I missing?
Forgive me all for the silly question :-s
I would set it as a <list ...> as in my book two orderlines with the same characteristics are identical (.Equals should return true) - also, many consider the order(!) of orderlines to be important. :-)
The reason for this is that bag mapping is compatible with a .NET IList<T> mapping as Yads answered. Collections are typically mapped that way to avoid having to reference Iesi.Collections in your classes.
With NHibernate 2.1, you can use set mapping by declaring the collection as ICollection<T> and initializing it as a HashSet<T>. Using NHibernate 3 I believe you can declare the collection as ISet<T>.
I agree most real world collections should be mapped as sets.
The reason most people implement it as a bag is because they can use the built in List<> class or IList<> interface to represent their collection.

How can I map "insert='false' update='false'" on a composite-id key-property which is also used in a one-to-many FK?

I am working on a legacy code base with an existing DB schema. The existing code uses SQL and PL/SQL to execute queries on the DB. We have been tasked with making a small part of the project database-engine agnostic (at first, change everything eventually). We have chosen to use Hibernate 3.3.2.GA and "*.hbm.xml" mapping files (as opposed to annotations). Unfortunately, it is not feasible to change the existing schema because we cannot regress any legacy features.
The problem I am encountering is when I am trying to map a uni-directional, one-to-many relationship where the FK is also part of a composite PK. Here are the classes and mapping file...
CompanyEntity.java
public class CompanyEntity {
private Integer id;
private Set<CompanyNameEntity> names;
...
}
CompanyNameEntity.java
public class CompanyNameEntity implements Serializable {
private Integer id;
private String languageId;
private String name;
...
}
CompanyNameEntity.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.jboss.org/dtd/hibernate/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
<class name="com.example.CompanyEntity" table="COMPANY">
<id name="id" column="COMPANY_ID"/>
<set name="names" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
<key column="COMPANY_ID"/>
<one-to-many entity-name="vendorName"/>
</set>
</class>
<class entity-name="companyName" name="com.example.CompanyNameEntity" table="COMPANY_NAME">
<composite-id>
<key-property name="id" column="COMPANY_ID"/>
<key-property name="languageId" column="LANGUAGE_ID"/>
</composite-id>
<property name="name" column="NAME" length="255"/>
</class>
</hibernate-mapping>
This code works just fine for SELECT and INSERT of a Company with names. I encountered a problem when I tried to update and existing record. I received a BatchUpdateException and after looking through the SQL logs I saw Hibernate was trying to do something stupid...
update COMPANY_NAME set COMPANY_ID=null where COMPANY_ID=?
Hibernate was trying to dis-associate child records before updating them. The problem is that this field is part of the PK and not-nullable. I found the quick solution to make Hibernate not do this is to add "not-null='true'" to the "key" element in the parent mapping. SO now may mapping looks like this...
CompanyNameEntity.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.jboss.org/dtd/hibernate/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
<class name="com.example.CompanyEntity" table="COMPANY">
<id name="id" column="COMPANY_ID"/>
<set name="names" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
<key column="COMPANY_ID" not-null="true"/>
<one-to-many entity-name="vendorName"/>
</set>
</class>
<class entity-name="companyName" name="com.example.CompanyNameEntity" table="COMPANY_NAME">
<composite-id>
<key-property name="id" column="COMPANY_ID"/>
<key-property name="languageId" column="LANGUAGE_ID"/>
</composite-id>
<property name="name" column="NAME" length="255"/>
</class>
</hibernate-mapping>
This mapping gives the exception...
org.hibernate.MappingException: Repeated column in mapping for entity: companyName column: COMPANY_ID (should be mapped with insert="false" update="false")
My problem now is that I have tryed to add these attributes to the key-property element but that is not supported by the DTD. I have also tryed changing it to a key-many-to-one element but that didn't work either. So...
How can I map "insert='false' update='false'" on a composite-id key-property which is also used in a one-to-many FK?
I think the annotation you are looking for is:
public class CompanyName implements Serializable {
//...
#JoinColumn(name = "COMPANY_ID", referencedColumnName = "COMPANY_ID", insertable = false, updatable = false)
private Company company;
And you should be able to use similar mappings in a hbm.xml as shown here (in 23.4.2):
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/example-mappings.html
"Dino TW" has provided the link to the comment Hibernate Mapping Exception : Repeated column in mapping for entity which has the vital information.
The link hints to provide "inverse=true" in the set mapping, I tried it and it actually works. It is such a rare situation wherein a Set and Composite key come together. Make inverse=true, we leave the insert & update of the table with Composite key to be taken care by itself.
Below can be the required mapping,
<class name="com.example.CompanyEntity" table="COMPANY">
<id name="id" column="COMPANY_ID"/>
<set name="names" inverse="true" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
<key column="COMPANY_ID" not-null="true"/>
<one-to-many entity-name="vendorName"/>
</set>
</class>

NHibernate Many to Many delete all my data in the table

I would love to thank #Stefan Steinegger and #David helped me out yesterday with many-to-many mapping.
I have 3 tables which are "News", "Tags" and "News_Tags" with Many-To-Many relationship and the "News_Tags" is the link table.
If I delete one of the news records, the following mappings will delete all my news records which have the same tags. One thing I need to notice, I only allowed unique tag stored in the "Tag" table.
This mapping make sense for me, it will delete the tag and related News records, but how can I implement a tagging system with NHibernate?
Can anyone give me some suggestion? Many thanks.
Daoming.
News Mapping:
<class name="New" table="News" lazy="false">
<id name="NewID">
<generator class="identity" />
</id>
<property name="Title" type="String"></property>
<property name="Description" type="String"></property>
<set name="TagsList" table="New_Tags" lazy="false" inverse="true" cascade="all">
<key column="NewID" />
<many-to-many class="Tag" column="TagID" />
</set>
</class>
Tag Mapping:
<class name="Tag" table="Tags" lazy="false">
<id name="TagID">
<generator class="identity" />
</id>
<property name="TagName" type="String"></property>
<property name="DateCreated" type="DateTime"></property>
<!--inverse="true" has been defined in the "News mapping"-->
<set name="NewsList" table="New_Tags" lazy="false" cascade="all">
<key column="TagID" />
<many-to-many class="New" column="NewID" />
</set>
</class>
When I run into trouble like that, the first thing I twiddle with is the cascade option.
As far as I know, the mapping is correct (I'm using mapping files that look exactly the same). The problem is the cascade attribute: the "all" option forces NHibernate to propagate each action on an entity to the instances of the collection. In your case, when you delete a news item all related tags are deleted too.
You probably should use "none" (in that case you'll eventually end up with some unused tags in the database) or "delete-orphans" (on the news item side - use "none" on the tag side).
Use the cascade option "save-update".
The option "all" will cascade deletes, which you do not want in this case. But you the option "none" will require that the Tag entity is already persisted which I guess might not always be the case.
So by setting the cascade to "save-update" new Tags till be inserted in the Tags table and in the link table News_Tags, but when you remove a tag from a News entity it will only be removed from the link table not the Tags table.

In nHibernate, can I map an abstract base class to a collection?

I have a base class for content items in a CMS I'm building. It's currently marked abstract because I only want derived classes to be instantiated. Derived classes like BlogPost, Article, Photo, etc. are set up as a joined subclass to my ContentBase class in nHibernate.
I'm trying to set up a many-to-many mapping between this class and a Tag class. I want to have a collection of Tags on the ContentBase class, and a collection of ContentBase items on the tag class.
Will nHibernate allow me to map the abstract ContentBase class as a collection on the Tag class? I'm assuming not since it wouldn't be able to instantiate any instances of this class when reconstituting a Tag entity from the db. I really don't want to have to have to use a collection of content items per type (e.g. TaggedBlogPosts, TaggedArticles, etc.) on the Tag class.
The whole reason I'm doing this is because logically, a content item can have many tags, and 1 tag can belong to multiple content items. in order for nHibernate to manage the relationships for me in a mapping table, I believe I have to set up a many-to-many association and add the Tag to the ContentBase.Tags collection and then the content item to the Tags.TaggedContentItems collection before the mapping table entry is created in nHibernate.
Here are my mappings for reference:
<class name="CMS.Core.Model.Tag,CMS.Core" table="bp_Tags">
<id column="TagName" name="TagName" type="String" unsaved-value="">
<generator class="assigned" />
</id>
<bag name="_taggedContentList" table="bp_Tags_Mappings" inverse="true" cascade="save-update" lazy="true">
<key column="TagName" />
<many-to-many class="CMS.Core.Model.ContentBase,CMS.Core" column="Target_Id" />
</bag>
</class>
<class name="CMS.Core.Model.ContentBase,CMS.Core" table="bp_Content">
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native"></generator>
</id>
<property name="SubmittedBy" column="SubmittedBy" type="string" length="256" not-null="true" />
<property name="SubmittedDate" column="SubmittedDate" type="datetime" not-null="true" />
<property name="PublishDate" column="PublishDate" type="datetime" not-null="true" />
<property name="State" column="State" type="CMS.Core.Model.ContentStates,CMS.Core" not-null="true" />
<property name="ContentType" column="ContentType" type="CMS.Core.Model.ContentTypes,CMS.Core" not-null="true" />
<bag name="_tagsList" table="bp_Tags_Mappings" lazy="false" cascade="save-update">
<key column="Target_Id" />
<many-to-many class="CMS.Core.Model.Tag,CMS.Core" column="TagName" lazy="false" />
</bag>
...
<joined-subclass name="CMS.Core.Model.BlogPost,CMS.Core" table="bp_Content_BlogPosts" >
<key column="Id" />
<property name="Body" type="string" column="Body" />
<property name="Title" type="string" column="Title" />
</joined-subclass>
...
I would also assume that Hibernate would need to instantiate the base class, which makes sense, as there is data directly linked to it in the DB.
It looks like your data entities are in a separate assembly from your main application. The point of not instantiating the base is from a business point of view, so if you make the constructor internal to the Core assembly, does that accomplish what you want? If not, it may be helpful to ask yourself: who am I protecting this functionality from?
NHibernate should allow you to map the abstract class as a collection as long as you map the abstract class itself (it looks like you have).
An alternative is to change your strategy to the Table-per-class-hierachy approach. This puts all your content into a single table with a discriminator column to define the type of content. NHibernate knows how to materialize each content type based on the discriminator. The downside is that:
Each unique property of the concrete classes has to be of type nullable.
This table can get unwieldy if there are a lot of properties specific to each concrete class.
The table-per-class-hierachy is the preferred default mapping strategy (per NHibernate in Action). I would start with the approach and modify it to table-per-subclass when the need is certain.
A whole separate solution would be keep the mapping as is and rely on query to get the content by tag without worrying about the tags keeping a reference to a collection of content.