NH-Noob getting frustrated at saving a unidirectional association

23 views
Skip to first unread message

fquednau

unread,
Jul 11, 2008, 4:38:31 AM7/11/08
to nhusers
Hello people,
sparing you the details of my lifeline so far, let me confront you
with what is having me scratching my head.

I cannot get my unidirectional association of Customer->Addresses to
save. Since I cannot attach a small solution here the very simple
setup in text:

Customer:
public class Customer
{
public Customer()
{
addresses = new List<Address>();
}

private IList<Address> addresses;

public virtual Guid Id { get; set; }
public virtual string Name { get; set; }

public virtual Customer AddAddress(Address adr)
{
addresses.Add(adr);
return this;
}

public virtual IList<Address> Addresses
{
get { return addresses; }
}
}
Address:
public class Address
{
protected Guid id;
}

Mappings:
<class name="Customer" table="Customer" >
<id name="Id" column="ID">
<generator class="guid" />
</id>
<property name="Name" column="Name" not-null="true" />
<list name="Addresses" access="nosetter.camelcase" cascade="all">
<key column="CustomerID" />
<index column="Posn" />
<one-to-many class="Address" />
</list>
</class>

<class name="Address" table="Address">
<id name="id" column="ID" access="field">
<generator class="guid" />
</id>
</class>

Part of my my test:
Customer c = new Customer() { Name = "Hugo" };
c.AddAddress(new Address());
...
using (var s = sf.SessionFactory.OpenSession())
using (var tx = s.BeginTransaction())
{
s.Save(c);
tx.Commit();
}

Fails:
Cannot insert the value NULL into column 'Posn', Console output from
NHibernate:
INSERT INTO dbo.Customer (Name, ID) VALUES (@p0, @p1); @p0 = 'Hugo',
@p1 = 'ea9c30d7-782d-430a-9442-502ddd7978f5'
INSERT INTO dbo.Address (ID) VALUES (@p0); @p0 = '3010ae19-8807-476d-
ba4d-ac67f0167b35'

I'm using Nhibernate2Beta1, DB Sqlserver 2005 (express), which is
shaped like this:
Table Customer with ID (uniqueidentifier, PK), Name (nchar(10), not
null)
Table Address with ID (uniqueidentifier, PK), Posn (int, not null),
CustomerID (uniqueidentifier, FK -> Customer.ID)

I have seen examples on the internet that look very similar, notably
here: http://sdesmedt.wordpress.com/2006/09/04/nhibernate-part-4-mapping-techniques-for-aggregation-one-to-many-mapping/

Then there is a passage that disrupts my confidence in the NHdocs 6.4
(one-to-many): "Very Important Note: If the <key> column of a <one-to-
many> association is declared NOT NULL, NHibernate may cause
constraint violations..."
Is that something that applies here? Do I really need to make the
above bidirectional? Can anyone enlighten me so that I can go to
weekend without heartpain and sadness?

Take care you lot,
Frank

Ayende Rahien

unread,
Jul 11, 2008, 4:57:32 AM7/11/08
to nhu...@googlegroups.com
 <list name="Addresses" access="nosetter.camelcase" cascade="all" inverse="true">

fquednau

unread,
Jul 11, 2008, 6:17:50 AM7/11/08
to nhusers
Thanks for the response, sadly it doesn't change anything. Test fails
with the same reason. I have added attribute inverse="true" to the
list node in the Customer mapping file. I had played with this
yesterday, but it was late, so there was a chance I was blind to
something. But this very toned down example also seems invariant to
the inverse attribute.

Cheers
Frank

On Jul 11, 10:57 am, "Ayende Rahien" <aye...@ayende.com> wrote:
>  <list name="Addresses" access="nosetter.camelcase" cascade="all" *
> inverse="true*">
> >http://sdesmedt.wordpress.com/2006/09/04/nhibernate-part-4-mapping-te...

Wolfgang Trog

unread,
Jul 11, 2008, 7:10:59 AM7/11/08
to nhu...@googlegroups.com
The problem afaik is that hibernate does the following:

1) insert Customer (all properties of the customer object)
2) insert Address (all properties of the address object)
3) update Address (association with key and index )

The only solutions I know of are, either declaring the columns as "NULL" or
using a bidirectional association.

--
Wolfgang

fquednau

unread,
Jul 11, 2008, 8:17:49 AM7/11/08
to nhusers
I have tried the following changes (compared to the list in my first
post)

Customer (Changed method to AddAddress):
public virtual Customer AddAddress(Address adr)
{
adr.Customer = this;
addresses.Add(adr);
return this;
}

Address (added field for Customer):
internal virtual Customer Customer { get; set; }

Mapping:
Address: Added many-to-one element as such:
<many-to-one name="Customer" class="Customer" column="CustomerID" />

I also had to change the Posn column on table Address to not-null,
default = 0

Now the inserts work and the original test case worked. However, NH
will never touch the Posn column. I.e. changing the test case to:
Customer c = new Customer() { Name = "Hugo" };
c
.AddAddress(new Address())
.AddAddress(new Address());
...
Assert.AreEqual(2, c.Addresses.Count);

Fails - the Addresses list only contains one element (must have to do
with the fact that both entries have a '0' in the Posn column).

I also tried removing default value and having Posn not null: Now the
select after insert fails:
"null index column for collection:
NH.Playground.Lib.Domain.Customer.Addresses"

* Is my assumption correct that a unidirectional Customer.Addresses
relationship + NH + actual DB constraints on FK = no can do?
I could live with it but I can't say that I love it.
* This is not like a weird scenario or something. Where am I failing?

Cheers
Frank
> here:http://sdesmedt.wordpress.com/2006/09/04/nhibernate-part-4-mapping-te...

Markus Zywitza

unread,
Jul 11, 2008, 10:19:27 AM7/11/08
to nhu...@googlegroups.com
Attached a test project of mine.

I used AR, but it creates hbm files you can see in the bin folder.
Ordering of the list works fine, and although the pos and FK fields
are not null, I can work with the generated DDL.

Perhaps you can see a difference with your problematic mapping by
comparing the projects.

-Markus

ClassLibrary1.zip

fquednau

unread,
Jul 12, 2008, 1:01:39 PM7/12/08
to nhusers
Hi Markus,
thanks for your zip - it embarked me on an adventure of getting the
castle project to build on my machine. I have used windsor a number of
times, but never ActiveRecord.
Trying to build the trunk, the build failed - I had the Framework 2.0
SDK (and therefore al.exe) missing. Finally my SQLExpress installation
broke for I do not know what reason, had to reinstall and fiddle, then
sort out your project and now I am in the position to answer you (one
of those days...) :)

As you said your tests run fine. I would think that the main reason
for my failure was that my FK from Address->Customer was not null,
while NH needs it to be (as Wolfgang said). Anyway, failures are great
for learning, but it is slightly shocking coming from an ADO
background that certain data consistency checks on the DB need to be
deactivated for this stuff to take off well.

Cheers, happy weekend
Frank


On Jul 11, 4:19 pm, "Markus Zywitza" <markus.zywi...@gmail.com> wrote:
> Attached a test project of mine.
>
> I used AR, but it creates hbm files you can see in the bin folder.
> Ordering of the list works fine, and although the pos and FK fields
> are not null, I can work with the generated DDL.
>
> Perhaps you can see a difference with your problematic mapping by
> comparing the projects.
>
> -Markus
>
>  ClassLibrary1.zip
> 4KDownload

James Kovacs

unread,
Jul 14, 2008, 11:08:35 PM7/14/08
to nhu...@googlegroups.com
Yes, due to the way that NH works with collections of entities, the position column needs to be nullable as you noted. NH is creating the persistent entities (i.e. Customer and Address) and then updating the Address row to create the relationship between the two.

One thing to note... Often when you think you need an IList<T>, what you really want is a ISet<T>. IList<T> is an ordered, non-unique collection. ISet<T> is an unordered, unique collection. In many domain models, you really mean a ISet<T>. (i.e. Do you expect duplicate addresses in Customer.Addresses and do you care about order. Probably not in both cases.) NHibernate uses Iesi.Collections.ISet<T> and Iesi.Collections.HashedSet<T>.

http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/06/12/mapping-collections-in-nhibernate-part-1.aspx

HTH,
James
--
James Kovacs, B.Sc., M.Sc., MCSD, MCT
Microsoft MVP - C# Architecture
http://www.jameskovacs.com
jko...@post.harvard.edu
403-397-3177 (mobile)

Brian Chavez

unread,
Aug 24, 2008, 9:28:28 PM8/24/08
to nhusers
Well, contrary to what Ayende said... correct me if I'm wrong, but..
if you're trying to maintain the index of a collection, you cannot set
inverse="true" or you'll get nulls in the index column.... inverse
must be "false" which tells NHibernate that the relationship is owned
by the entity defining the <list> (which also owns and maintains the
ordered index) ... so set <list inverse="false">.... In addition,
your child table must allow nulls on the parent FK and Posn columns of
the child table because NH will perfrom the insert then immedately
update the relationship with the correct Posn and FK.

------------------------
Brian Chavez
http://bchavez.bitarmory.com

On Jul 11, 1:57 am, "Ayende Rahien" <aye...@ayende.com> wrote:
>  <list name="Addresses" access="nosetter.camelcase" cascade="all" *
> inverse="true*">
> >http://sdesmedt.wordpress.com/2006/09/04/nhibernate-part-4-mapping-te...
>
> > Then there is a passage that disrupts my confidence in the NHdocs 6.4
> > (one-to-many): "Very Important Note: If the <key>  column of a <one-to-
> > many> association is declared NOT NULL, NHibernate may cause
> > constraint violations..."
> > Is that something that applies here? Do I really need to make the
> > above bidirectional? Can anyone enlighten me so that I can go to
> > weekend without heartpain and sadness?
>
> > Take care you lot,
> > Frank- Hide quoted text -
>
> - Show quoted text -

David Gardiner

unread,
Aug 24, 2008, 11:15:32 PM8/24/08
to nhusers
I'm a bit surprised that NHibernate doesn't appear to handle this
differently if in the original example, the Customer object was
already persisted (and had a primary key) that the Address objects
couldn't do a single INSERT with the foreign key set in one operation.

-david

On Jul 15, 12:08 pm, "James Kovacs" <jkov...@post.harvard.edu> wrote:
> Yes, due to the way that NH works with collections of entities, the position
> column needs to be nullable as you noted. NH is creating the persistent
> entities (i.e. Customer and Address) and then updating the Address row to
> create the relationship between the two.
>
> One thing to note... Often when you think you need an IList<T>, what you
> really want is a ISet<T>. IList<T> is an ordered, non-unique collection.
> ISet<T> is an unordered, unique collection. In many domain models, you
> really mean a ISet<T>. (i.e. Do you expect duplicate addresses in
> Customer.Addresses and do you care about order. Probably not in both cases.)
> NHibernate uses Iesi.Collections.ISet<T> and Iesi.Collections.HashedSet<T>.
>
> http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/06/12/mapp...
>
> HTH,
> James
> --
> James Kovacs, B.Sc., M.Sc., MCSD, MCT
> Microsoft MVP - C# Architecturehttp://www.jameskovacs.com
> jkov...@post.harvard.edu
> 403-397-3177 (mobile)

Brian Chavez

unread,
Aug 25, 2008, 12:16:19 AM8/25/08
to nhu...@googlegroups.com
Yes, was surprised also. I came across the same behavior just now while
working with an list index.

NHibernate should have enough information about the entity to take care of
it in 1 shot. But I guess that's why everyone tells you need to use
transactions :D

----------------------------------------------
Brian Chavez
Bit Armory, Inc.
http://www.bitarmory.com
Reply all
Reply to author
Forward
0 new messages