NHibernate : many-to-many collection - nhibernate

A customer can have several contact. The code below is working but... the contacts for this customer are deleted first and after inserted again. Is not possible to avoid this delete and just insert the new one ?
<class name="Customer" table="Customer">
<id name="Id">
<generator class="native" />
</id>
<property name="LastName" not-null="true" />
<bag name="Contacts" cascade="all-delete-orphan" table="CustomerContact">
<key column="Customer" not-null="true" />
<many-to-many class="Contact"/>
</bag>l
</class>
<class name="Contact" table="Contact">
<id name="Id">
<generator class="native" />
</id>
<property name="LastName" not-null="true" />
<many-to-one name="Customer" column="Customer" not-null="true" />
</class>
public virtual void AddContact(Contact contact)
{
Contacts.Add(contact);
contact.Customer = this;
}
When I do this code twice, to add 2 contacts :
Contact contact = new Contact() { LastName = "MyLastName" };
Customer customer = session.Get(customerId);
customer.AddContact(contact);
session.Update(customer);

You are using a bag for your collection, a bag can contain duplicates, does not maintain order and there is no way to identify an individual entry in the bag distinctly with SQL.
That is why NH removes and inserts all assigned entities. Use a set if it suits your requirements instead.

This typically happens when NH lost the persistence information about the collection. It assumes that you changed the whole collection. To update the database efficiently, it removes all items in one query (delete ... where customer = 5) and inserts the new items.
You probably don't return the collection provided from NH.
Typical mistake:
IList<Contact> Contacts
{
get { return contacts; }
// wrong: creates a new List and replaces the NH persistent collection
set { contacts = value.ToList(); }
}
By the way, you should make the collection inverse, since it is a redundancy to the contact.Customer relation:
<bag name="Contacts" cascade="all-delete-orphan" table="CustomerContact" inverse="true">
<key column="Customer" not-null="true" />
<many-to-many class="Contact"/>
</bag>

Related

Nhibernate query with null child

I need to retrieve all the users with a valid Wish property (so not null). This is the xml of my class:
<class name="Project.Engine.Domain.User,Project.Engine" table="Users" lazy="true">
<id name="UserID" column="UserID">
<generator class="native" />
</id>
<property name="Firstname" column="Firstname" type="string" not-null="true"
length="255" />
<property name="Lastname" column="Lastname" type="string" not-null="true"
length="255" />
<property name="Email" column="Email" type="string" not-null="true"
length="255" />
<one-to-one name="Wish" cascade="all" property-ref="UserID"
class="Project.Engine.Domain.Wish, Project.Engine" />
</class>
The method to get all my users is the following:
public PagedList<User> GetAll(int pageIndex, int pageSize,
string orderBy, string orderByAscOrDesc)
{
using (ISession session = NHibernateHelper.OpenSession())
{
var users = session.CreateCriteria(typeof(User));
users.Add(Restrictions.IsNotNull("Wish"));
return users.PagedList<User>(session, pageIndex, pageSize);
}
}
As you can notice, I have added the Restriction on the child object. This doesn't work properly as the method return all users including the ones with Wish property as null. Any help?
this is the xml for child:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Project.Engine.Domain.Wish,Project.Engine" table="Wish" lazy="false">
<id name="WishID" column="WishID">
<generator class="native" />
</id>
<property name="UserID" column="UserID" type="int" not-null="true" length="32" />
<property name="ContentText" column="ContentText" type="string" not-null="false" length="500" />
<property name="Views" column="Views" type="int" not-null="true" length="32" />
<property name="DateEntry" column="DateEntry" type="datetime" not-null="true" />
</class>
</hibernate-mapping>
Well, there is a bug with one-to-one and null testing of the side which may not exist. I had already encountered it but forgot about it. The property-ref just render it a bit more tricky to diagnose, but it does exist on actual one-to-one too.
Here is its corresponding issue in NHibernate tracking tool.
Workaround: test for null state of an non-nullable property of Wish, like Wish.Views.
Forgive the wild guess on test syntax, I do not use nhibernate-criteria anymore since years, but try by example:
public PagedList<User> GetAll(int pageIndex, int pageSize,
string orderBy, string orderByAscOrDesc)
{
using (ISession session = NHibernateHelper.OpenSession())
{
var users = session.CreateCriteria(typeof(User));
users.Add(Restrictions.IsNotNull("Wish.Views"));
return users.PagedList<User>(session, pageIndex, pageSize);
}
}
Using linq-to-nhibernate, I confirm this workaround works with my own projects, which gives by example:
// The "TotalAmount != null" seems to never be able to come false from a
// .Net run-time view, but converted to SQL, yes it can, if TransactionRecord
// does not exist.
// Beware, we may try "o.TransactionsRecord != null", but you would get struck
// by https://nhibernate.jira.com/browse/NH-3117 bug.
return q.Where(o => o.TransactionsRecord.TotalAmount != null);
I maintain my other answer since you may consider using a many-to-one instead, especially since you do not have made a bidirectionnal mapping (no corresponding constrained one-to-one in Wish) in addition to not having an actual one-to-one. many-to-one does not suffer of the bug.
one-to-one mapping using property-ref is not an "actual" one-to-one, and usually this is a sign a many-to-one mapping should be used instead.
Maybe this is not related to your trouble, but you may give it a try.
An "actual" one-to-one has the dependent table primary key equals to the parent table primary key. (Dependent table, Wish in your case, would have a foreign primary key, UserId in your case. See this example.)
I have sometime "played" with 'one-to-one property-ref', and I always ended giving it up due to many issues. I replaced that with more classical mappings, either changing my db for having an actual one-to-one, or using a many-to-one and living with a collection on child side though it would always contain a single element.

How can I map to a joined subclass with a different column than the id of parent?

I am working with a brownfield database and am trying to configure a subclass map which joins to its subclasses with a column other than that of the specified id. The login table has a primary key column login_sk which I'd like to use as its id. It joins to two tables via a login_cust_id column (to make things more fun the corresponding columns in the adjoining tables are named differently). If I setup login_cust_id as the id of the UserMap it joins to its subclasses as expected. For what I hope are obvious reasons I do not want to use login_cust_id as the id for my User objects.
public class UserMap : ClassMap<IUser>
{
public UserMap()
{
Table("login");
Id(x => x.Id).Column("login_sk"); // want to setup map like this
// if used instead this works for subclass joining / mapping
// Id(x => x.Id).Column("login_cust_id");
// would prefer to only reference login_cust_id for subclass mapping
}
}
public class CustomerUserMap : SubclassMap<CustomerUser>
{
public CustomerUserMap()
{
Table("customer");
Map(c => c.DisplayName, "cust_mail_name");
Map(c => c.RecordChangeName, "cust_lookup_name");
KeyColumn("cust_id");
}
}
public class EntityUserMap : SubclassMap<EntityUser>
{
public EntityUserMap()
{
Table("entity");
Map(c => c.DisplayName, "entity_name");
KeyColumn("entity_id");
}
}
What I'd like to do is only use the login_cust_id column when joining to subclasses. Is there a fluent mapping setting that allows me to specify this? If not a fluent mapping is there a regular NHibernate XML mapping that would work? I'd prefer to not even map the column and only use it for joining if possible. If it helps there is a potential discriminator column login_holder_type which indicates which table to join to.
It did occur to me to setup an IClassConvention but after poking at the passed IClassInstance I could not determine any settings which would help me.
public class UserIdConvention : IClassConvention, IClassConventionAcceptance
{
public void Apply(IClassInstance instance)
{
// do something awesome with instance.Subclasses to
// specify the use of login_cust_id for subclass joining...
}
public void Accept(IAcceptanceCriteria<IClassInspector> criteria)
{
criteria.Expect(x => typeof(User).Equals(x.EntityType));
}
}
The lack of a populated Subclasses collection for the passed instance caused me to look for a more specific inspector which IParentInspector appears to be. Unfortunately Fluent NHibernate does not appear to have corresponding implementations for IParentInstance, IParentConvention or IParentConventionAcceptance like it does for IJoinedSubclassInspector. While I could probably implement my own before I do I wanted to ensure I wasn't barking up the wrong tree.
Is this sort of subclass id adjustment even possible? Am I missing something obvious in either my map or the Fluent NHibernate Conventions namespace? How can I map to a joined subclass with a different column/property than the id of parent?
I was able to think of three possible solution to your problem please see my findings below.
Solution 1: Discriminator based mapping with Join
My initial idea was to use a discriminator based mapping for modelling the inheritance, with each sub-class containing a join with a property ref, i.e
<class name="IUser" abstract="true" table="login">
<id name="Id" column="login_sk">
<generator class="identity"/>
</id>
<discriminator column="login_holder_type" not-null="true" type="System.String"/>
<subclass name="CustomerUser" discriminator-value="Customer">
<join table="customer" >
<key column="cust_id" property-ref="login_cust_id" />
<property name="DisplayName" column="cust_mail_name"/>
<property name="RecordChangeName" column="cust_lookup_name" />
</join>
</subclass>
<subclass name="EntityUser" discriminator-value="Entity">
<join table="entity" >
<key column="entity_id" property-ref="login_cust_id" />
<property name="CompanyName"/>
</join>
</subclass>
</class>
Unfortunately at this time this feature is supported in Hibernate but not in NHibernate. Please see here and here for the outstanding tickets. Some work has gone towards adding this feature which can be seen on this fork on github.
Solution 2: Discriminator based mapping with Many-to-One
Another option is to still use the discriminator based mapping, but use a many-to-one mapping within each of the sub-classes, which would allow you to join on the foreign key using a property-ref. This has the disadvantage of requiring separate classes for all of the properties in your customer and entity tables but is a workable solution.
<class name="IUser" abstract="true" table="login">
<id name="Id" column="login_sk">
<generator class="identity"/>
</id>
<discriminator column="login_holder_type" not-null="true" type="System.String"/>
<subclass name="CustomerUser" discriminator-value="Customer">
<many-to-one name="CustomerProps" property-ref="login_cust_id" />
</subclass>
<subclass name="EntityUser" discriminator-value="entity">
<many-to-one name="EntityProps" property-ref="login_cust_id" />
</subclass>
</class>
<class name="CustomerProps" Table="customer" >
<id name="Id" column="cust_id">
<generator class="assigned"/>
</id>
<property name="DisplayName" column="cust_mail_name"/>
<property name="RecordChangeName" column="cust_lookup_name" />
</class>
<class name="EntityProps" Table="entity" >
<id name="Id" column="entity_id">
<generator class="assigned"/>
</id>
<property name="CompanyName"/>
</class>
Solution 3: Discriminator based mapping with Joins to Updatable Views
The final option is to create an Updatable View in the DB for the customer and entity tables which contains the login_sk field. You can then use Join within each sub-class as you wouldn't require the property-ref.
<class name="IUser" abstract="true" table="login">
<id name="Id" column="login_sk">
<generator class="identity"/>
</id>
<discriminator column="login_holder_type" not-null="true" type="System.String"/>
<subclass name="CustomerUser" discriminator-value="Customer">
<join table="customerView" >
<key column="login_sk" />
<property name="DisplayName" column="cust_mail_name"/>
<property name="RecordChangeName" column="cust_lookup_name" />
</join>
</subclass>
<subclass name="EntityUser" discriminator-value="Entity">
<join table="entityView" >
<key column="login_sk" />
<property name="CompanyName"/>
</join>
</subclass>
</class>

What is the correct way to define many-to-many relationships in NHibernate to allow deletes but avoiding duplicate records

I've been fighting with an NHibernate set-up for a few days now and just can't figure out the correct way to set out my mapping so it works like I'd expect it to.
There's a bit of code to go through before I get to the problems, so apologies in advance for the extra reading.
The setup is pretty simple at the moment, with just these tables:
Category
CategoryId
Name
Item
ItemId
Name
ItemCategory
ItemId
CategoryId
An item can be in many categories and each category can have many items (simple many-to-many relationship).
I have my mapping set out as:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Category" lazy="true">
<id name="CategoryId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Items" table="ItemCategory" cascade="save-update" inverse="true" generic="true">
<key column="CategoryId"></key>
<many-to-many class="Item" column="ItemId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Item" table="Item" lazy="true">
<id name="ItemId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Categories" table="ItemCategory" cascade="save-update" generic="true">
<key column="ItemId"></key>
<many-to-many class="Category" column="CategoryId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
My methods for adding items to the Item list in Category and Category list in Item set both sides of the relationship.
In Item:
public virtual IList<Category> Categories { get; protected set; }
public virtual void AddToCategory(Category category)
{
if (Categories == null)
Categories = new List<Category>();
if (!Categories.Contains(category))
{
Categories.Add(category);
category.AddItem(this);
}
}
In Category:
public virtual IList<Item> Items { get; protected set; }
public virtual void AddItem(Item item)
{
if (Items == null)
Items = new List<Item>();
if (!Items.Contains(item))
{
Items.Add(item);
item.AddToCategory(this);
}
}
Now that's out of the way, the issues I'm having are:
If I remove the 'inverse="true"' from the Category.Items mapping, I get duplicate entries in the lookup ItemCategory table.
When using 'inverse="true"', I get an error when I try to delete a category as NHibernate doesn't delete the matching record from the lookup table, so fails due to the foreign key constraint.
If I set cascade="all" on the bags, I can delete without error but deleting a Category also deletes all Items in that category.
Is there some fundamental problem with the way I have my mapping set up to allow the many-to-many mapping to work as you would expect?
By 'the way you would expect', I mean that deletes won't delete anything more than the item being deleted and the corresponding lookup values (leaving the item on the other end of the relationship unaffected) and updates to either collection will update the lookup table with correct and non-duplicate values.
Any suggestions would be highly appreciated.
What you need to do in order to have your mappings work as you would expect them to, is to move the inverse="true" from the Category.Items collection to the Item.Categories collection. By doing that you will make NHibernate understand which one is the owning side of the association and that would be the "Category" side.
If you do that, by deleting a Category object it would delete the matching record from the lookup table as you want it to as it is allowed to do so because it is the owning side of the association.
In order to NOT delete the Items that are assigned to a Category object that is to be deleted you need to leave have the cascade attribe as: cascade="save-update".
cascade="all" will delete the items that are associated with the deleted Category object.
A side effect though would be that deleting the entity on the side where the inverse=tru exists will thow a foreign key violation exception as the entry in the association table is not cleared.
A solution that will have your mappings work exactly as you want them to work (by the description you provided in your question) would be to explicitly map the association table.
Your mappings should look like that:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Category" lazy="true">
<id name="CategoryId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="ItemCategories" generic="true" inverse="true" lazy="true" cascade="none">
<key column="CategoryId"/>
<one-to-many class="ItemCategory"/>
</bag>
<bag name="Items" table="ItemCategory" cascade="save-update" generic="true">
<key column="CategoryId"></key>
<many-to-many class="Item" column="ItemId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Item" table="Item" lazy="true">
<id name="ItemId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="ItemCategories" generic="true" inverse="true" lazy="true" cascade="all-delete-orphan">
<key column="ItemId"/>
<one-to-many class="ItemCategory"/>
</bag>
<bag name="Categories" table="ItemCategory" inverse="true" cascade="save-update" generic="true">
<key column="ItemId"></key>
<many-to-many class="Category" column="CategoryId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
As it is above it allows you the following:
Delete a Category and only delete the entry in the association table without deleting any of the Items
Delete an Item and only delete the entry in the association table without deleting any of the Categories
Save with Cascades from only the Category side by populating the Category.Items collection and saving the Category.
Since the inverse=true is necessary in the Item.Categories there isn't a way to do cascading save from this side. By populating the Item.Categories collection and then saving the Item objec you will get an insert to the Item table and an insert to the Category table but no insert to the association table. I guess this is how NHibernate works and I haven't yet found a way around it.
All the above are tested with unit tests.
You will need to create the ItemCategory class mapping file and class for the above to work.
Are you keeping the collections in synch? Hibernate expects you, I believe, to have a correct object graph; if you delete an entry from Item.Categories, I think you have to delete the same entry from Category.Items so that the two collections are in sync.

NHibernate many-to-one association refuses to load

I have the following mapping, the many-to-one property 'Message' has a corresponding one-to-many association in the 'RootMessage' class.
<class name="IMessageReceipt" lazy="false" table="MessageReceipts" abstract="true">
<id name="Id">
<generator class="guid.comb"></generator>
</id>
<discriminator column="Discriminator"/>
<property name="Address" />
<property name="Status" />
<property name="MarkedAsDeleted" />
<many-to-one name="Message" column="MessageId" class="RootMessage"
not-found="ignore"/>
<subclass name="MessageReceipt" lazy="false" discriminator-value="1">
</subclass>
</class>
The many-to-one association refuses to load when using the criteria api (all I get is NULL), here is an example of a query:
List<IMessageReceipt> list;
using (var tx = Session.BeginTransaction())
{
var criteria = Session.CreateCriteria(typeof (IMessageReceipt));
criteria.Add(Restrictions.Eq("Address", address));
criteria.Add(Restrictions.Eq("Status", status));
criteria.SetFirstResult(0);
criteria.SetMaxResults(quantity);
list = criteria.List<IMessageReceipt>().ToList();
tx.Commit();
}
return list;
Any ideas?
Ok so after almost a day of chagrin I have the solution. NHibernate doesn't automatically assume a bi-directional association between two entities even if you have mappings between both. You need to imperatively declare the associations in your code before persisting. Thus:
message.Receipts = receipts;
foreach (var receipt in receipts)
{
receipt.Message = message;
}
Session.Save(message);
tx.Commit();
Also inverse="true" should be applied to the side with the collection member:
<set name="Receipts" inverse="true" cascade="save-update">
<key column="MessageId"></key>
<one-to-many class="IMessageReceipt"/>
</set>

Insert into child table with NHibernate 2.0.1

I started using NHibernate this week (struggling). I have a small application with 3 tables (I use 2 for now). Table currency and table country here are the mapping files.
<class name="dataprovider.Country,dataprovider" table="country">
<id name="CountryId" column="country_id" unsaved-value="0">
<generator class="native"/>
</id>
<!--<bag name="BatchList" inverse="true" lazy="true" >
<key column="country_id" />
<one-to-many class="Batch" />
</bag>
<bag name="PrinterList" inverse="true" lazy="true" >
<key column="country_id" />
<one-to-many class="Printer" />
</bag>-->
<many-to-one name="CurrencyId" column="currency_id" class="Currency" cascade="save-update"/>
<!--<property column="currency_id" name="Currency_Id"/>-->
<property column="name" name="Name"/>
<property column="region" name="Region" />
<property column="comments" name="Comments"/>
</class>
The currency mapping file:
<class name="dataprovider.Currency, dataprovider" table="currency">
<id name="CurrencyId" column="currency_id" >
<generator class="native"/>
</id>
<bag name="CountryList" inverse="true" lazy="true" >
<key column="currency_id" />
<one-to-many class="Country" />
</bag>
<!--<bag name="DenominationList" inverse="true" lazy="true" >
<key column="currency_id" />
<one-to-many class="Denomination" />
</bag>-->
<property column="name" name="Name"/>
<property column="authorizer" name="Authorizer" />
<property column="date_created" name="DateCreated" type="DateTime" not-null="true" />
<property column="comments" name="Comments" />
The many to one relationship that country hold create an attribute of the type Currency in the country persistence class. Now while my test can_add_currency and can_add_country succeeded (I export the schema) I have null value in the table country on the field currency_id.
Here is the test code:
[Test]
public void can_add_new_country()
{
CountryManager cm = new CountryManager();
Country country = new Country();
//country.CurrencyId = CurrencyManager.GetCurrencyById(1);
country.CurrencyId = new CurrencyManager().GetCurrencyById(1);
country.Name = "POUND";
country.Region = "ENGLAND";
country.Comments = "the comment";
cm.Add(country);
using(ISession session = _sessionFactory.OpenSession())
{
Country fromdb = session.Get<Country>(country.CountryId);
Assert.IsNotNull(fromdb);
Assert.AreNotSame(fromdb, country);
}
}
public Currency GetCurrencyById(int currency_id)
{//GetCurrencyById from CurrencyManger class
try
{
using(ISession session = NHibernateHelper.OpenSession())
{
return session.Get<Currency>(currency_id);
}
} catch (Exception ex)
{
return null;
}
}
The question is: how to insert into table country with the currency_id of an existing currency_id from the table currency?
How do you guys/gals do it? I'm seriously stuck and a 2 day small project is taking me one week now.
Set cascade="save-update" on your bag name="CountryList". If that doesn't work, it may be helpful to post your code for CountryManager.Add() to see how the saving is taking place.
In response to your second question, if I understand it correctly, this is how NHibernate treats mapped collections:
You mapped the collection as lazy, so loading the object will not load all the elements of the collection at the same time. Instead, when you first access the collection NHibernate will query the database to fill the collection and return it. So, when you first do something like:
var countries = currency.CountryList;
or
foreach (Country country in currency.CountryList)
NHibernate will silently execute a query similar to:
SELECT * FROM country WHERE currency_id = ?
And then build a collection of Country objects to return (and cache so that the query is not run again).
Basically, through the mapping file you've already told NHibernate all about your two entities (Country and Currency) and how they are related so it knows how to build the queries to access the data. Similarly, it keeps track of what was in the collection so when you add or remove items, it can compare what was changed and execute the appropriate INSERT or REMOVE statements.
So, the way to use collections mapped by NHibernate is to use them just as you would with a normal .NET collection. Add and remove items at your will. Just when you are finished, make sure you tell NHibernate to persist the changes you made to the database, either by calling session.Save() or session.Delete() on every item you add/remove or (if you have set cascading, like you have) simple call session.Save() on the parent object that contains the collection.
My cascade was rather on the this side:
<many-to-one name="CurrencyId" column="currency_id" class="Currency" cascade="save-update"/>
After I changed it it wasn't working at first even though I rebuild the solution. Then I did another test from the Currency_Test class: can_get_currency_by_id which call the same function GetCurrncyById and I could have an object while from can_add_new_country the same function returns null.
Then I realise that the ExportSchema of either Country_Test [Setup] or Currency_Test is not creating the database on time for the can_add_new_product to have a currency object. That's why it's returning null object.
Now not to abuse; but can you tell me how to use IList<Counrty>CountryList? I don't know if I put it well. From my understanding it should store all country objects using the same currency (currency_id reference). How does NHibernate do that?