We have a userobject split between two tables, a person table and user table.
public UserMap()
{
Table("Person");
Id(x => x.PersonId, "PersonId");
Map(x => x.Name, "Name");
Join("User", s => {
s.Fetch.Join();
s.KeyColumn("PersonId");
s.Map(x => x.Username, "Username");
s.Map(x => x.Password, "Password");
});
}
When doing a insert, nHibernate will first insert Name into the Person table, and then Username and Password into the User table.
The problem arise when the insertion into the User-table fails (like trying to insert a user with a username that is already taken). The transaction fails, but the insertion into the Person table is not rolled back.
public User Save(User user)
{
var session = SessionHelper.GetCurrent();
using (var dbTransaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(user);
dbTransaction.Commit();
}
catch (Exception)
{
dbTransaction.Rollback();
throw;
}
}
return user;
}
Our SessionFactory is also set up with Fluent NHibernate
public ISessionFactory BuildSessionFactory(string connectionString)
{
return Fluently.Configure().Database(OracleClientConfiguration.Oracle10.ConnectionString(c => c.Is(connectionString))
.Provider<OracleDriverConnectionProvider>()
.Driver<OracleDataClientDriver>()
.ShowSql)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>())
.BuildSessionFactory();
}
I have put the logfile from a failing session in a gist. The User entity is a little more complex in real life, and there are some authentication stuff going on, so the log file want match 1:1 with the painted picture...
Seems like a similar problem was solved here. NHibernate will not revert you changes in the session when you rollback your transaction, i.e if you flush your session after the rollback, you will experience the problems you describe. To ensure no changes in your entities, when you rollback, you have to also close the session.
Add User object in Person Object and Save Person Object only. Use Cascade.All() in Mapping file. or Second option is use TransactionScope.
Instead of closing the session, you can use session.Clear() after you have roleld back the transaction, which will remove all pending writes and make session as good as new.
That did the trick for my application.
Related
I get a stackoverflow exception when trying to remove a composite object from a collection (but only when i wrap it inside a transaction). However getting the object and deleting it on the session, instead of the collection, and saving the parent works.
The code looks like this:
using (var session = sessionProvider.GetSession())
{
using (var transaction = session.BeginTransaction())
{
var products = session.Query<MyObject>().Select(x => x.MyObjectId > 0).ToList();
//Would fail
var variant = session.Query<MyObject>().FirstOrDefault(x => x.Name == "some name that exists");
var parent = MyObject.Parent;
parent.RemoveMyObject(variant);
session.Save(parent);
//Doesn't fail
//session.Delete(variant);
transaction.Commit();
}
session.Flush();
}
And my mapping looks like this:
HasMany(x => x.MyObject).AsSet().Cascade.AllDeleteOrphan().Inverse().KeyColumn("MyObjectId").BatchSize(2000);
References(x => x.MyObject).Column("MyObjectId");
What should i do to fix this ? It should work in both ways. And it is, as said, only when there's a transaction around. Both approaches works without a transaction.
Best regards
Morten
I have parent and child objects BOOKLET and DEMOGRAPHICS_INFO in my Oracle database mapped as follows in my data layer:
public BookletMapping()
{
Table("BOOKLET");
Id(x => x.Id, m => m.Column("ID");
...
ManyToOne(x => x.DemographicsInfo, m => m.Column("DEMOGRAPHICS_INFO_ID"));
}
public DemographicsInfoMapping()
{
Table("DEMOGRAPHICS_INFO");
Id(x => x.Id, m => m.Column("ID");
...
}
I have intentionally left out the DemographicsInfo relationship to the Booklet because I don't need to traverse my entities that direction. The ManyToOne relationship will actually be a one-to-one.
I have written a test to ensure I can create a DemographicsInfo and immediately assign it to its parent Booklet, and that looks like this:
[Test]
public void ShouldSaveCorrectEntity()
{
var booklet = _unitOfWork.Get<Booklet>(4);
var demInfo = new DemographicsInfo();
_unitOfWork.Insert(demInfo);
booklet.DemographicsInfo = demInfo;
_unitOfWork.Save();
demInfo.Id.ShouldNotEqual(0);
}
When I call Save(), I get the following exception:
{"ORA-02291: integrity constraint (<schema>.BOOKLET_DEMOGRAPHICS_INFO_FK1) violated - parent key not found\n"}
This is because the demInfo object is not given an Id upon Insert(). My Insert implementation looks like this:
public void Insert<T>(T entity)
{
using (var transaction = _session.BeginTransaction())
{
_session.Save(objectToSave);
_session.Flush();
transaction.Commit();
}
}
Where _session is an NHibernate ISession. Because I have both Saved the new entity (it persists successfully) and Flushed my session, I would expect my demInfo variable to have an Id, but it remains 0, which is a foreign key violation when I try to save my parent object. Am I overlooking a step here? Should I rethink my pattern for adding a new child to an existing parent?
I have solved my issue. As it turns out, the Oracle convention of using Sequences for Id column values and populating them using Triggers on row insert disallows NHibernate from learning the Id upon persisting an entity. I added a Sequence Generator to my Id maps and Insert() now updates the Id on my transient entity upon save so I can assign the DemographicsInfo to my Booklet.DemographicsInfo.
Id(x => x.Id, m =>
{
m.Column("ID");
m.Generator(Generators.Sequence, a => a.Params(new { sequence = "SEQ_TABLE_ID" }));
});
Now my test passes with no exceptions.
I have a scenario similar to this:
Asp.NET MVC 4 website using nHibernate Session Per Request.
The session is injected using Ninject onto a Repository with the Get and Save methods.
There are a lot of articles talking about Session Per Request and saying that is the way to do things on a web application.
But i'm having problems implementing logic like this one:
Read Data From Database
Alter Entity information
Save to Database
Read another entity
Alter entity
Save ... but an EXCEPTION OCCURS
I want to show my view with a message to the user. But i have also to refresh the resulting web page,
so i have also to read some information from the database.
According to nHibernate documentation, the session with the exception must be discarded Documentation Here
But i can't find any articles about the best way to proceed here.
What's the best approach for this situation?. I will have to inject a new Session to my repository object?.
Thanks.
You can create a new session from the SessionFactory property of the original session. You can access the original session object by either exposing it in the repository class or injecting it into the controller. Then you can create a new repository with the new session.
I do this in some of my Actions where I expect unique key violations to occur and I have to reload lookup data in the model. Here's an example:
public ActionResult Create(MeasuresEditView model)
{
if (ModelState.IsValid)
{
using (var txn = _session.BeginTransaction())
{
try
{
var measure = new Measure { Code = model.Code };
_session.Save(measure);
txn.Commit();
return RedirectToAction("Index");
}
catch (UniqueKeyException)
{
txn.Rollback();
var msg = string.Format("A measure with the code '{0}' already exists, please enter a different code or cancel.", model.Code);
ModelState.AddModelError("Code", msg);
}
catch (Exception ex)
{
if (txn.IsActive)
{
txn.Rollback();
}
log.Error("Create", ex);
throw;
}
}
}
// have to rebuild selectlist on post in new txn in case it was rolled back
using (var session = _session.SessionFactory.OpenSession())
using (var txn = session.BeginTransaction())
{
SetProductGroupSelectList(session, model, manualId);
txn.Commit();
}
return View(model);
}
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.
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.