My application is a multi-user podcast aggregator, using NHibernate 2.1 (and .NET 4.0 if that changes anything). I'm also a complete NHibernate n00b.
Details of each podcast are only stored once, and Users subscribe to podcasts, so there is a many-to-many mapping between podcasts and users which I have mapped in my database with a Subscriptions table:
Subscriptions(UserId, FeedId)
I also have a UserFeedItems table which stores per-user-per-item information:
UserFeedItems(UserId, FeedItemId, IsNew, ListenCount, etc.)
My object model is a bit different:
class Podcast {
IList<PodcastFeedItem> FeedItems { get; set; }
bool HasNew {
get {
// return true if any of the FeedItems are new
}
}
}
class PodcastFeedItem {
bool IsNew { get; set; }
}
class User {
IList<PodcastFeed> Subscriptions { get; set; }
}
What mappings do I need in NHibernate to correctly map the relational model to the object model? Have I gone to far by defining those "link" tables listed above? Ordering may be important here, as obviously I need to keep the feeds organised in descending chronological order.
I've read through the documentation on collection mapping, but I'm struggling to fit the examples to my own scenario.
Its a classic Many-to-Many where a Podcast can have many subscribers and a subscriber can subscribe to many podcasts. You map it like this:
<class name="App.Core.Domain.User, App.Core" table="users">
<set name="SubscribedPodcasts" table="subscriptions" inverse="false" cascade="all">
<key column="userid"/>
<many-to-many class="App.Core.Domain.Podcasts, App.Core" column="podcastid"/>
</set>
</class>
<class name="App.Core.Domain.Podcast, App.Core" table="podcasts">
<set name="SubscribedUsers" table="subscriptions" inverse="false" cascade="all">
<key column="podcastid"/>
<many-to-many class="App.Core.Domain.User, App.Core" column="userid"/>
</set>
</class>
If you really want to store the index in the database rather than having NH order the results (I have never had to do this as its better to order by a column and have NH provide your indexes). then add
<index-many-to-many
column="column_name"
class="ClassName"
/>
To the mapping
You don't need the lookup table. The answer to this question provides the details on how to perform a many to many in NHibernate
Related
I have recently inherited a project at work that contains NHibernate. I am extremely new to it and have to make a modification to one of the mappings. I've read through the documentation here and I'm still not sure how to do this or if my understanding/terminology is even correct.
So given the following table structure I need a bag that will get me the ProjectName:
User
UserID (PK)
ProjectUser
UserID (PK, FK User.UserID)
ProjectID (PK, FK Project.ProjectID)
Project
ProjectID (PK)
ProjectName
Here is the existing bag mapping and it correctly returns the ProjectID, but now I'm trying to understand how I need to modify it to return both the ProjectID and the ProjectName:
<bag name="Projects" table="ProjectUser" lazy="true" inverse="true" cascade="save-update">
<key column="UserId"></key>
<many-to-many class="Project" column="ProjectID"></many-to-many>
</bag>
Well, your mapping seems to be correct, i.e. already returning the ProjectName. To be sure please, check that the object Project is mapped like this:
<class name="Project" table="Project">
<id name="Id" column="ProjectID" generator class="native"/>
<!-- above as the Id we have mapping for column ProjectId
below the C# Name will contain the column ProjectName -->
<property name="Name" column="ProjectName" />
<!-- related Users to this Project -->
<bag name="Users" table="ProjectUser" lazy="true" >
<key column="ProjectID"></key>
<many-to-many class="User" column="UserID" />
</bag>
</class>
And the Project would be like this
public class Project
{
public virtual int Id { get; set;}
public virtual string Name { get; set;}
public virtual IList<User> Users { get; set;}
...
So, having this in place, we should be able to use the User:
public class User
{
public virtual IList<Project> Projects { get; set;}
...
mapped and loaded by NHibernate like this
user = session.Get<User>(x) // id of searched user
foreach(var project in user.Projects)
{
var name = project.Name;
var id = project.Id;
...
}
NOTES:
In case of many-to-many there obviously could/should be <bag> mapping on both sides - Project and User. But only one of them can have inverse="true". So in this case Project.Users does not have that.
The cascade setting on many-to-many is doing (most likely) different thing than one would expect. It is not related to the pairing table but to the second end of that mapping.
Cascading of the pairing object is done out of the box. It does not have to be mapped, in fact it cannot be mapped or turned off... Other words I would suggest to remove that cascade, unless you really want to change the Project in persistence, if you are working with some User.
Check also:
23.2. Author/Work
How to do it without many-to-many ... with explicit pairing object as a mapped Entity: Nhibernate: How to represent Many-To-Many relationships with One-to-Many relationships? or Am I doing many to many incorrectly when using fluent nhibernate?
My many-to-many relationship does not involve the standard "joining-table" approach, in which a table stores the "FK1-to-FK2" relationships.
Instead, I'm "loosely" joining to a legacy read-only view as follows:
Appointment class (based on the Appointment table)
int AppointmentId (PK)
int OrderId
List<LegacyOrder> LegacyOrders
LegacyOrder class (based on the LEGACY_ORDERS_VIEW view in our legacy system)
int OrderId (composite PK)
int VersionNumber (composite PK)
An Appointment can have many (versions of a) LegacyOrder.
A LegacyOrder can have many Appointments, but this relationship is not important in our application.
I want to populate the LegacyOrders property with all LegacyOrders for the specified OrderId.
My attempt at mapping is as follows:
<class name="Appointment" table="Appointments" lazy="true">
<bag name="Orders" table="LEGACY_ORDERS_VIEW" inverse="true">
<key column="OrderId" />
<many-to-many class="LegacyOrder" column="ORDER_ID" />
</bag>
</class>
....but I'm getting "could not execute query" exceptions due to invalid SQL.
I think the table referred to in the <bag> mapping should be the "joining table".... but I don't have one.
I'm fairly sure my mapping approach is fundamentally wrong.... what's the right way to go about it?
Edit:
Thanks Radim: perhaps a better name for LegacyOrder would be LegacyOrderVersion: each record in that view corresponds to a "version" of an order, rather than an order.
i.e. An order may be for 100 units, then when say 20 units are collected, another record is written with the same OrderId but for 80 units. (I did warn you it was legacy :)
If an Appointment (in the new system) can retrieve all related LegacyOrderVersions, then it can derive useful properties such as CurrentLegacyOrderVersion and OriginalLegacyOrderVersion.
FWIW: this works great for me:
<class name="Appointment" table="Appointments" lazy="true">
<bag name="Orders" inverse="true">
<key property-ref="OrderId" column="ORDER_ID" />
<one-to-many class="LegacyOrder" />
</bag>
</class>
One way how to solve this a bit challenging DB structure, could be with the property-ref feature. See more details here: 5.1.10. many-to-one, working even for our many-to-many scenario.
So firstly we have to map the property, which we will use as a reference:
<class name="Appointment" table="Appointments" lazy="true">
...
// the column name is coming from the Appointment table
<property name="OrderId" column="ORDER_ID" />
So, now we have mapped the OrderId - the property (column) - which we will use to map the <bag>.
Well, honestly, now I am not sure what your thoughts were. In case that LegacyOrder would have one column mapped as key (the Order_ID) we can do it like this.
<bag name="Orders" table="LEGACY_ORDERS_VIEW" inverse="true">
<key column="ORDER_ID" property-ref="OrderId" />
<many-to-many class="LegacyOrder" formula="ORDER_ID" />
</bag>
But that's not reasonable, because the Order_Id is not unique. In fact the LegacyOrder view, does not seem to be the entity at all. It could be some real intermediate structure.
I would say, that what the pairing view Legacy_orders_view represents, is the map (dictionary) saying: The Order with ID == X, had these Versions.
This information, the int Version numbers, is the only thing/information I can find out as really interesting. The OrderId is representing still the same Order
Anyhow, with the proeprty-ref and more detailed knowledge what you need to achieve we can at the end have:
// I. Map
public virtual IDictionary<int, Order> OrderMap { get; set; }
above the Version will play the role of the Key, the Order is questinable, because it will be the same Order as the OrderId says
// II. Version collection
public virtual IList<int> OrderVersions { get; set; }
in this case we will get set of int numbers related to the OrderId. Seems to be the only interesting message we can get.
III. There must be more information, about your entity/DB model. Why does the Legacy_orders_view exists at all? What would we like to get from that "relation" at the end?
I have customer and BankAccount tables. CustomerID is a foreign key in the BankAccount table. Here we have to do with one to many relationship. I have done mapping for CustomerInfo and BankAccountInfo. I have attached with the question post.
Here we want to do with collection using Iesi.collections. Does anyone have ideas on how to do this in Nhibernate?
To do Collection mapping start reading this documentation: Chapter 6. Collection Mapping
In case that you will use the Iesi collections, most suitable could be ISet<>, which is designed to allow only unqiue values to be inserted:
public class Customer
{
public virtual Iesi.Collections.Generic.ISet<BankAccount> BankAccounts { get; set; }
}
And the mapping could be like this
<set name="BankAccounts" inverse="true" lazy="true" table="BankAccount"
cascade="all" batch-size="25" >
<key column="CustomerId" />
<one-to-many class="BankAccount" />
</set>
Notes: This mapping has some redundant attributes, which can help to do smarter loading. E.g. batch-size will avoid 1+N loads, while doing 1 + N/25... cascade will implicitly solve the persistence of added items... read more in doc.
The complete ISet description: http://ayende.com/blog/3943/nhibernate-mapping-set
I was reading about inverse = true in one-to-many relation and was actually wondering where to set that property to true in a many-to-many relationship.
I know inverse is to point which entity/table is the owner of the relationship. In a one-to-many sitation this is actually quite simple. Buy how do you point a side in a many-to-many relation? Or is it not necessary?
Can you point both sides? Or do you have to choose one side? How do you actually decide which side that is?
If i think according to the owner of the relationship i think both sets have to set inverse=true because they both aren't the owner of relationship. But actually the other isn't the owner either. It's the table UsersTargets that is the owner.
Someone that is able to get me out of my mysery?
E.g.: A user can have many awards.
Note: It isn't necessary to know how many users have a specific award. So maybe i should make my model different? Anyway my code till now:
Domain:
public class User : Entity
{
...
public virtual ISet<Award> Awards { get; set; }
}
public class Award : Entity
{
...
public virtual ISet<User> Users { get; set; }
}
Mapping:
<-User.hbm.xml->
<set name="Awards" table="UsersAwards" cascade="all">
<key column="UserId"/>
<many-to-many class="Award" column="AwardId"/>
</set>
<-Award.hbm.xml->
<set name="Users" table="UsersAwards" cascade="all">
<key column="AwardId"/>
<many-to-many class="User" column="UserId"/>
</set>
Thx in advance.
Exactly one side of the relationship should not be marked inverse. That means that if you decide to remove the Award.Users property from your model, just make sure that Users.Awards is not marked inverse.
If you leave both collections in your model, and you use code like this to add / remove associations...
public class User
{
public virtual void Add(Award award)
{
award.Users.Add(this);
Awards.Add(award);
}
public virtual void Remove(Award award)
{
Awards.Remove(award);
award.Users.Remove(this);
}
}
... then it doesn't really matter which side you put inverse on - just make sure one side has it and the other side doesn't.
You should set the side you don't use for updating as inverse.
In your case, you probably add Awards to a User, not users to an Award. Therefore, Award.UserAwards would be inverse
Of course if you do add users to the award instead, just reverse what I just said.
Nhibernate users, professionals, gurus and developers are expected. Please help !!!
I want to realise a n:m relation between two classes. A student attends in more courses and a course consists of more students as members. I do a bidirectional association many-to-many with bag to get the both lists from each site.
The two Student and Course classes:
public class Student {
// Attributes........
[XmlIgnore]
public virtual IList MyCourses { get; set; }
// Add Method
public virtual void AddCourse(Course c)
{
if (MyCourses == null)
MyCourses = new List<Course>();
if (!MyCourses.Contains(c))
MyCourses.Add(c);
if (c.Members== null)
c.Members= new List<Student>();
if (!c.Members.Contains(this))
c.Members.Add(this);
}
public virtual void RemoveCourse(Course c)
{
if (MyCourses != null)
MyCourses.Remove(c);
if (c.Members!= null)
c.Members.Remove(this);
}
}
public class Course {
// Attributes........
[XmlIgnore]
public virtual IList Members { get; set; }
}
In database there are two tables t_Student, t_Course and a relation table tr_StudentCourse(id, student_id, course_id).
<class name="Student" table="t_Student" polymorphism="explicit">
.....
<bag name="MyCourses" table="tr_StudentCourse">
<key column="student_id" />
<many-to-many class="Course" column="course_id" not-found="ignore" />
</bag>
</class>
<class name="Course" table="t_Course" polymorphism="explicit">
.....
<bag name="Members" table="tr_StudentCourse" inverse="true">
<key column="course_id" />
<many-to-many class="Student" column="student_id" not-found="ignore" />
</bag>
</class>
Course was chosen as inverse in the bidirectional association. I did the same as example (Categorie, Item) in section 6.8 of nhibernate documentation. So I saved the student object after inserting a course in the list MyCourses by calling the Add/Remove-method.
Student st1 = new Student();
Course c1 = new Course();
Course c2 = new Course();
st1.AddCourse(c1);
st1.AddCourse(c2);
session.saveOrUpdate(st1);
That works fine, the st1, c1 and their relation (st1,c1) can be find in the database. The relation datasets are (id=1, st1.id, c1.id) and (id=2, st1.id, c2.id).
Then I add more courses to the object st1.
Course c3 = new Course();
st1.AddCourse(c3);
session.saveOrUpdate(st1);
I can see the 3 relation datasets, but the two old relations were deleted and new three were created with another new id. (id=3, st1.id, c1.id), (id=4, st1.id, c2.id) and (id=5, st1.id, c3.id). There are not dataset with id=1 and 2 more in relation table.
The same by deleting if I remove a course from student.MyCourse and then save the student object. All collection was also deleted and recreated a new list which less one deleted element. That problem makes the id in the relation table increates very fast and a have troble by doing a backup of relation.
I have looked some days in internet, documentation and forums to find out why the whole old collection was deleted and a new as created by each changing, but I was not successful. It is a bug from nhibernate mapping or did I do any wrong?
I am very very grateful to your help and answer.
Nhibernate documentation http://nhforge.org/doc/nh/en/index.htm
NHibernate can't create, delete or
update rows individually, because
there is no key that may be used to
identify an individual row.
By note from "6.2. Mapping a Collection"
As soon as you have id in tr_StudentCourse you can try using indexed collections, i.e. replace <bag> with <map> or similar and add <index> element to the mapping:
<index
column="id"
type="int"
/>
or even create a special entity for the relation table to use with <index-many-to-many>.
This is what I've found on the NHibernate website:
Hibernate is deleting my entire
collection and recreating it instead
of updating the table.
This generally happens when NHibernate
can't figure out which items changed
in the collection. Common causes are:
replacing a persistent collection
entirely with a new collection
instance passing NHibernate a manually
constructed object and calling Update
on it.
serializing/deserializing a
persistent collection apparently also
causes this problem.
updating a
with inverse="false" - in this case,
NHibernate can't construct SQL to
update an individual collection item.
Thus, to avoid the problem:
pass the same collection instance that
you got from NHibernate back to it
(not necessarily in the same session),
try using some other collection
instead of <bag> (<idbag> or <set>),
or try using inverse="true" attribute
for <bag>.