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?
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?
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. :)
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>
I also had this error and the solution was to wrap it in a transaction as Ben mentioned. I wasn't however, able to get Ben nor gilles27 code to work - probably because I'm new to generics and NHibernate. I created a slightly different implementation that works (using Fluent NHibernate v1.3):
public static ISession LocalDbSession = null;
public static void Save<T>(T toSave)
{
using (var transaction = LocalDbSession.BeginTransaction())
{
LocalDbSession.Save(toSave);
transaction.Commit();
LocalDbSession.Flush();
}
}
public static void Save<T>(IEnumerable<T> toSave)
{
using (var transaction = LocalDbSession.BeginTransaction())
{
foreach (T item in toSave)
{
LocalDbSession.Save(item);
}
transaction.Commit();
LocalDbSession.Flush();
}
}
i think instead of generator class="identitiy"/>
try generator class="guid.comp"
Related
I am trying to write a test for my NHibernate mappings that will automatically pick up and test any new mappings that get added.
At the moment I have a test that opens a session to a known test database then attempts to load the first entity of each type and asserts that it is not null.
This all works fine but it means that every time I add a new entity mapping, I need to remember to update the test.
So, what I want to do is to inspect the mappings and try to load one of each of the mapped entities, but the NHibernate Configuration object that the sessionfactory is built from is not visible to my test so I was wondering if there is a way to access a list of mapped entities from the session or do I need to expose the original Configuration instead?
You can get SessionFactory from Session and SessionFactory has method GetAllClassMetadata() which returns list of IClassMetadata. And from IClassMetadata you can get MappedClass (GetMappedClass())
But you will need some extra work to get subclasses. This code can help:
var metaData = this.session.SessionFactory.GetClassMetadata(baseClass);
if (metaData != null && metaData.HasSubclasses)
{
foreach (string entityName in ((NHibernate.Persister.Entity.IEntityPersister)metaData).EntityMetamodel.SubclassEntityNames)
{
var metadata = this.session.SessionFactory.GetClassMetadata(entityName);
result.Add(metadata.GetMappedClass(EntityMode.Poco));
}
}
I expose the configuration object and do a mapping that queries all of my entities like this. It will output all errors from each of my mappings.:
[TestMethod()]
public void AllNHibernateMappingsAreOkay()
{
bool failed = false;
log4net.Config.XmlConfigurator.Configure();
using (ISession session = SessionFactory.GetCurrentSession())
{
foreach (var s in SessionFactory.GetConfig().ClassMappings)
{
try
{
SessionFactory.GetCurrentSession().CreateQuery(string.Format("from {0} e", s.MappedClass.Name))
.SetFirstResult(0).SetMaxResults(50).List();
}
catch (Exception ex)
{
failed = true;
log.ErrorFormat("\r\n\r\n {0} \r\n {1} \r\n\r\n", ex.Message, ex.InnerException.Message);
}
}
}
Assert.IsFalse(failed, "One or more mappings have errors in them. Please refer to output or logs.");
}
if you have only one row per entity then you could issue session.QueryOver<object>().List();
Given the following
[Test]
public void VerifyMappings()
{
new PersistenceSpecification<Address>(Session)
.CheckProperty(x => x.AddressLine1, "190 House 12")
.VerifyTheMappings();
}
The following will attempt to do a read and write to the datbase, however it leaves the record. Is it possible to delete this record using the fluent framework?
Just use something like this in your [TearDown]:
var currentSession = NHibernateSession.Current;
if (currentSession.Transaction.IsActive) {
currentSession.Flush();
currentSession.Transaction.Rollback();
}
That will rollback the current transaction.
The entities and mappings I'm talking about in this question can be found here :)
Here is the context:
I have a parent view-model which helps to manage some entities, and which has its own session.
From this VM, I open another view-model (with its own session too), do some changements to the entity (add and/or remove children), and when I validate the changements, I commit the session and warns the first view-model to refresh the display:
public void Validate()
{
using (var tx = Session.BeginTransaction())
{
try
{
SelectedTeam.ClearRoster();
foreach (var teamPlayer in TeamPlayers)
SelectedTeam.AddPlayer(teamPlayer);
teamsRepository.SaveOrUpdate(SelectedTeam);
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
}
finally
{
if (tx.WasCommitted)
ServiceLocator.Current.GetInstance<Mediator>().NotifyColleagues(MediatorMessages.DisplayEntityInfos, SelectedTeam.Id);
}
}
}
Here is the faulted method of the parent VM:
public void RefreshEntitiesListAndDisplayEntityInfos(int selectedEntityId)
{
TEntity entity = entitiesRepository.Load(selectedEntityId);
Session.Refresh(entity);
//...
}
The exception is thrown at the Refresh line:
NHibernate.UnresolvableObjectException
And the message is:
No row with the given identifier exists[Emidee.CommonEntities.PlayerInTeam#3
I can open and change the entity multiple times, but it seems that the exception is thrown when I delete a children, then add another one, and finally delete another one.
After some readings on the web, it seems that's because when I refresh the entity, and because I changed the HasMany relationship (because I have deleted a player for example), NH tries to reload the deleted row.
I've tried to add a NotFound.Ignore statement on the HasMany in my mappings, I've tried to force a new query to the DB instead of a Load, but I still get this exception.
Does someone know how I could fix that?
Thanks in advance
Mike
This is a known behavior when refreshing objects with modified collections.
To force reload, change your method to do session.Evict with the entity as a parameter. This is the code we use in our base model class:
public T ReGet<T>(T entity) where T : IEntity
{
var id = entity.Id;
Session.Evict(entity);
return Session.Get<T>(id);
}
Well, I've just spotted the problem.
To update the players list of the team, I used to clear the list, and add the new players, before updating the entity.
Now, I update the list by removing and adding only the players who have been moved by the user, and I don't have any problems at all now.
That's weird. I don't know what was wrong before, but as least that works now.
I've run into some trouble with unique constraints in NHibernate.
I have a User entity that is mapped with a unique constraint for the Username property. What I want to do is to be able to check if a particular username exists before a new user is added and also before an existing user updates it's username.
The first scenario (adding a new user) works just fine. However, when I try to check if the username exists before updating an existing user, I get a constraint violation. Here's what the code for my Save method looks like.
public void Save<T>(T entity) where T : User
{
using (var session = GetSession())
using (var transaction = session.BeginTransaction())
{
try
{
CheckIfUsernameExists(entity);
session.SaveOrUpdate(entity);
session.Flush();
transaction.Commit();
}
catch (HibernateException)
{
transaction.Rollback();
throw;
}
}
}
The constraint is violated in the CheckIfUsernameExists() method and it looks like this:
public void CheckIfUsernameExists<T>(T entity) where T : User
{
var user = GetUserByUsername(entity);
if (user != null)
throw new UsernameExistsException();
}
private T GetUserByUsername<T>(T entity) where T : User
{
var username = entity.Username;
var idToExclude = entity.Id;
var session = GetSession();
var user = session.CreateCriteria<T>()
.Add(Restrictions.Eq("Username", username))
.Add(Restrictions.Not(Restrictions.IdEq(idToExclude)))
.UniqueResult() as T;
return user;
}
It is the session.CreateCriteria() line that causes it to crash resulting in an NHibernateException (SQLiteException) with the message "Abort due to constraint violation. column Username is not unique".
Is it related to the NHibernate cash? The entity that is passed to the save method has been updated with the desired username at the time session.CreateCriteria() is called.
Maybe I'm doing this all wrong (I am an NHibernate beginner) so please feel free to state the obvious and suggest alternatives.
Any help is much appreciated!
Hmm, I'm not sure about the core of the problem, but for the strategy of trying to see whether a user already exists, why do you need the ".UniqueResult()"?
Couldn't you just assume to get a list of users which match that username and which do not have the same id as your current user (obviously). Pseudo-code like I'd do something like this
public bool ExistsUsername(string username, int idToExclude)
{
IList<User> usersFound = someNHibernateCriteria excluding entries that have id = idToExclude
return (usersFound.Count > 0)
}
Two thoughts:
- Why don't you just SaveOrUpdate and see if you succeed. Is that not possible in your scenario?
- I've seen you mentioning SQLite. Is that your real production system, or just something you use for testing. If so, have you checked if it's SQLite that makes the problems, and the query works against a fully featured DBMS? - SQLite frequently makes that kind of problems, because it does not support every kind of constraint...
Are you sure the exception is thrown at CreateCriteria? Because, I don't see how you could get a SQLlite constraint exception from a select statement. I do virtually the same thing...
public bool NameAlreadyExists(string name, int? exclude_id)
{
ICriteria crit = session.CreateCriteria<User>()
.SetProjection(Projections.Constant(1))
.Add(Restrictions.Eq(Projections.Property("name"), name));
if (exclude_id.HasValue)
crit.Add(Restrictions.Not(Restrictions.IdEq(exclude_id.Value)));
return crit.List().Count > 0;
}
I would look at the order of the generated sql to see what's causing it. If that entity was loaded in that session, it could be getting updated before the query.
transaction.Rollback() doesn't remove your entity from session cache, use session.Evict() instead.
See:
- https://www.hibernate.org/hib_docs/nhibernate/html/performance.html#performance-sessioncache
Considering the following code blocks, why does call to HQL work but call to delete() not work? As a background, I'm using NHibernate over IBM.Data.DB2.Iseries driver. Come to find out, journaling on the AS400 is turned off so I can't use transactions. I'm not the AS400 admin or know anything about it so I don't know if having journaling turned off (not opening transactions) is causing this problem or not. Do I absolutely need the ability to open transactions if I'm calling Delete() or other NHibernate functions?
//This Does not work - no error and no deletes
public static void Delete(Object Entity)
{
using (ISession session = _sessionFactory.OpenSession())
{
//using(ITransaction tx = session.BeginTransaction())
//{
session.Delete(Entity);
//tx.Commit();
session.Close();
//}
}
}
//This does work
public static void Delete(Object Entity)
{
using (ISession session = _sessionFactory.OpenSession())
{
//commented out transaction control because throws error because
//journaling is not turned on the AS400
//using(ITransaction tx = session.BeginTransaction())
//{
session.CreateQuery("delete MyDAO p where p.MyDAOID = :MyDAOID").SetString("MyDAOID", ((MyDAO)Entity).MyDAOID.ToString()).ExecuteUpdate();
//}
}
}
Try calling session.Flush() after you delete, but before you close the session. And you don't need to explicitly close the session:
using (var session = ...)
{
entity.Delete();
session.Flush();
}
After delving further into this, I found that this works but why?:
public static void Delete(Object Entity)
{
using (ISession session = _sessionFactory.OpenSession())
{
MyDAO p = session.Get<MyDAO>(Entity.ID);
session.Delete(p);
session.Flush();
}
}
Here is my scenario: I have previously queried for a list of objects which I populated into a Grid. For that query process, I opened/closed a session. Upon calling my delete function, I take that object and pass it into this function for a separate open/close session process. To stir things up a bit, I added the call to this new delete asking it to get the object first (the one I want deleted), then call delete. Now it sends the delete query to the database and the object is in fact deleted. Why is this?
Is this because my original query of objects was opened within a different session and then deleted in another? Does this have anything to do with the unit of work. Should I open my session for my grid, leave it open until my form closes so all deletes work inside that session? I was under the impression that sessions should be opened/closed quickly. I'm confused.