tags:

views:

595

answers:

4

I'm having trouble saving an entity into an SQL Server 2005 database. I'm using NHibernate 2.0.0.3002 for my persistence layer. The mapping is typical, with an integer ID, as follows

<id name="Id" unsaved-value="0">
  <column name="Id"/>
  <generator class="identity" />
</id>

I've omitted the rest for brevity. The application is using a repository class with a generic save method as follows

public void Save(T toSave)
{
    Save(new T[] { toSave });
}

public void Save(IEnumerable<T> toSave)
{
    using (ISession session = SessionFactory.OpenSession())
    {
        foreach (T item in toSave)
        {
            session.SaveOrUpdate(item);
        }
        session.Flush();
    }
}

When calling SaveOrUpdate on the session, an exception is thrown with a message of "null identifier". When I check the database the row has been inserted with all the correct values, so I think the problem is when NHibernate tries to set the Id property of the entity with the value returned by @@IDENTITY. I can see through SQL Profiler that @@IDENTITY is being called so I don't understand why the exception is thrown.

Has anyone else had this problem?

A: 

i think instead of generator class="identitiy"/>

try generator class="guid.comp"

alice7
I think you mean guid.comb, not guid.comp. In any case that won't work as I am working with an integer key, not a GUID key.
gilles27
A: 

Identity is pretty discouraged by the NHibernate developers.. the main issue we ran into is that until you Flush you don't get an Id. In your Int case HiLo would be a good replacement.

That being said I think you actually want to do this...

<id name="Id" column="Id" unsaved-value="0">
   <generator class="identity"/>  
</id>
ShaneC
I have tried using the XML shown here but the problem remains. I think that XML is equivalent to mine above, just a little more terse.
gilles27
+1  A: 

Both Save and Delete must happen in a transaction and the transaction must be committed at the end.

like so:

public void Save(IEnumerable<T> toSave)
{
 using (ISession session = SessionFactory.OpenSession())
 {
  ITransaction transaction = Session.BeginTransaction();

  foreach (T item in toSave)
  {
   session.SaveOrUpdate(item);
  }

  transaction.Commit();   
  session.Flush();
 }
}

please note: you'll want to wrap that in a using and properly rollback... also, placement of where you're opening and committing the transaction will matter based on your scenario. You should also close the transaction when you're done...

Also, can you elaborate on where the exception is happening? It sounds like you're saving a parent and then the child is throwing because the parent's id is null? Or, is it actually throwing on saving the parent?

Ben
I've wrapped it all in a transaction as advised, with rollback called if an exception is thrown. This has fixed my problem thank you.The record does have relationships with two other records, but they both exist (and therefore have IDs) long before this record is created so I don't believe that could be related to the problem.I am surprised that wrapping it all in a transaction has fixed it but perhaps I need to do some research into the best ways of using NHibernate's sessions and transactions. Any pointers would be welcome.
gilles27
There really is no best way, imho. It all depends on your specific design. Typically, I have aggregate roots that specify cascades to the children. I then wrap the save at the aggregate root level in a transaction. For instance. Answers have comments so I would do an answer.AddComment(comment); answerRepository.Save(answer).My new favorite method for wrapping the save in a transaction comes from FNH's (old) source....will post in a bit.
Ben
' public void Save(T entity) { WithinTransaction(() => Session.SaveOrUpdate(entity)); } private void WithinTransaction(Action action) { ITransaction transaction = Session.BeginTransaction(); try { action(); transaction.Commit(); } catch (Exception) { transaction.Rollback(); throw; } finally { transaction.Dispose(); } } '
Ben
Thanks for your advice Ben. I will give this a try.
gilles27
+1  A: 

It is maybe helpfull to configure log4net so that you can log and view the actions that NHibernate is doing ...

I once had a problem with NHibernate using Access as well, and was able to solve it by setting up logging so that I could exactly pinpoint the cause of the problem.

The error-message that I received, was different from yours, but this is the article in where I describe how I solved my problem. Perhaps it could be helpfull for you to. :)

Frederik Gheysels
I have used Profiler to inspect the SQL being executed by NHibernate and it all appears correct. It even calls @@IDENTITY to retrieve the new ID of the record, but after that the exception is thrown.Just to be clear, I am using SQL Server, not Access.
gilles27
I thought you once said in your TS that you're using Access ? Anyway, nice chap to downgrade all the people that tried to help you, but didn't provide the correct solution ...
Frederik Gheysels
I apologise if I offended you by downgrading your answer. However I believe in the spirit of this site that incorrect or misleading answers should be voted down in order that the best content is at the top.Voting your answer down is in no way critical of you or the blog post that you linked to. Thank you for trying to help.
gilles27
+1 This answer fixed my issue when moving from SQL Server 2005 testing to SQL Compact Edition. Spent an hour going mad as it worked on one but not the other. Read your post, updated my configuration and no error on save with CE version!
Wayne