Remove record from NHibernate session

Topics: Writing modules
Jul 16, 2012 at 5:09 PM

Hi,

My module uses a posted form to call an action on a controller to delete a non-content data record. It first deletes all the counties associated through a foreign key and then deletes the country. Here is the action:


       

 [HttpPost, Admin, ActionName("Delete")]
        public ActionResult DeletePOST(CountryRecord country)
        {           
            foreach (CountyRecord county in CountyRepo.Fetch(c=>c.CountryRecord.Id==country.Id))
            {
                CountyRepo.Delete(county);
            }
           
            CountryRepo.Delete(country);
            OrchardServices.Notifier.Add(NotifyType.Information, T("Country '{0}' deleted successfully", country.CountryName));
            return RedirectToAction("Index");
        }

 

However, when it calls the Delete on the country repository, it throws this error:

   

a different object with the same identifier value was already associated with the session

 

None of the items actually get deleted from the tables.

Any help or thoughts on this would be greatly appreciated.

Developer
Jul 16, 2012 at 6:24 PM

Just a wild idea, what happens if you call Flush() on your repository after the delete loop?

Jul 16, 2012 at 11:26 PM

I actually thought that would fix it as well, but already tried it this afternoon. (to no avail) I also tried all combinations of the CascadeAllDeleteOrphan attribute.

Coordinator
Jul 17, 2012 at 12:03 AM

Transaction got rolled back because there are apparently other objects pointing to the record you're trying to delete. Hard to say what.

Jul 17, 2012 at 12:50 AM

That's why I run through the counties and delete them first. There's nothing else in my system that is connected with the record. But even if I run through the counties and step through the delete call, nothing get's deleted at the database. So it doesn't commit the delete and that is why the country record still has items attached to it when I try to delete it. So that is exactly my question. What do I need to do to commit one delete before proceeding on to the next.

Coordinator
Jul 17, 2012 at 12:56 AM

You do not want to prematurely commit the transaction.

Do you have the detailed stack trace?

Jul 17, 2012 at 8:51 AM

Sure, here it is:


An unhandled exception has occurred and the request was terminated. 
Please refresh the page. If the error persists, go back a different object with the same identifier value was already associated
with the session: 2, of entity: Addresses.Models.CountryRecord NHibernate.NonUniqueObjectException: a different object with the same
identifier value was already associated with the session: 2, of entity:
Addresses.Models.CountryRecord at NHibernate.Engine.StatefulPersistenceContext
.CheckUniqueness(EntityKey key, Object obj) at NHibernate.Event.Default
.DefaultDeleteEventListener.OnDelete(DeleteEvent event, ISet transientEntities)
at NHibernate.Event.Default.DefaultDeleteEventListener.OnDelete(DeleteEvent event)
at NHibernate.Impl.SessionImpl.FireDelete(DeleteEvent event)
at NHibernate.Impl.SessionImpl.Delete(Object obj)
at Orchard.Data.Repository`1.Delete(T entity)
in
C:\Orchard Development\src\Orchard\Data\Repository.cs:line 107
at Orchard.Data.Repository`1.Orchard.Data.IRepository<T>.Delete(T entity)
in
C:\Orchard Development\src\Orchard\Data\Repository.cs:line 44
at Addresses.Controllers.CountriesAdminController.DeletePOST(CountryRecord country)
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)

Coordinator
Jul 17, 2012 at 11:07 AM

I can't see your model so it's hard to say if that would be the problem, but my guess would be that you also have a counties collection on the country record that's not managed right and that's still holding references to the counties, or something like that. You may also be doing other, incompatible things with that same object somewhere else in the code. Doing a Merge on the nHibernate session may help according to some posts I see about this error message.

Jul 17, 2012 at 2:57 PM

Here are my two models:

 

    public class CountryRecord
    {
        public CountryRecord()
        {
            CountyRecords = new List<CountyRecord>();
        }
        public virtual int Id { get; set; }
        public virtual string CountryName { get; set; }
        public virtual IList<CountyRecord> CountyRecords { get; set; }
    }

 

    public class CountyRecord
    {
        public virtual int Id { get; set; }
        public virtual string CountyName { get; set; }
        public virtual CountryRecord CountryRecord { get; set; }
    }

 

And here is the Delete actions in the CountryController:

 

        //DELETE
        [HttpGet, Admin]
        public ActionResult Delete(int countryId)
        {
            var country = CountryRepo.Get(countryId);
            if (country == null)
            {
                return new HttpNotFoundResult("Couldn't find the country with ID " + countryId.ToString());
            }
            return View(country);
        }

        [HttpPost, Admin, ActionName("Delete")]
        public ActionResult DeletePOST(CountryRecord country)
        {            
            foreach (CountyRecord county in CountyRepo.Fetch(c=>c.CountryRecord.Id==country.Id))
            {
                CountyRepo.Delete(county);
            }
            
            CountryRepo.Delete(country);
            OrchardServices.Notifier.Add(NotifyType.Information, T("Country '{0}' deleted successfully", country.CountryName));
            return RedirectToAction("Index");
        }

 

And here is the Delete view:

@model Addresses.Models.CountryRecord
<div class="manage">
@using (Html.BeginFormAntiForgeryPost("Delete"))
{
    <h2>Are you sure you want to delete this country and ALL its counties?</h2>
    @Html.HiddenFor(m => m.Id);
    @Html.HiddenFor(m => m.CountryName);
    @Html.ActionLink(T("Cancel").Text, "Index", "CountriesAdmin", new { AreaRegistration = "Addresses" }, new { style = "float:right; padding:4px 15px;" })
    <button class="button primaryAction" style="float:right;">@T("Confirm")</button>
}
</div>

I can't find any documentation/posts about merging the nHibernate session and I can't see even where to access the nHibernate session? Is that nesesary anyway. It seems like a simple thing that I want to do. I just want to delete a Non-Content item and all its cascading relations. How would you normally do it?

Coordinator
Jul 17, 2012 at 6:46 PM

The session can be got from ISessionLocator. I'm also wondering if you need to manage that list of counties so that they don't keep references to your deleted counties. Shouldn't be the case but may be worth checking.

Jul 18, 2012 at 9:08 AM

Thanks Bertrand. Sorry, but I don't know anyhing about ISessionLocator. Where can I find documentation on using it?

But more importantly, what do you meen by "manage that list of counties so that they don't keep references to your deleted counties." How do I do that?

Is there not an example of relational Non-Content orchard data somewhere so that I can see how someone else has done it? It seems impossible that this has not been a requirement for at least one other person using Orchard.

Please, I need an example or some documentation.

Jul 19, 2012 at 9:38 AM

Please, I need some help.

Developer
Jul 20, 2012 at 12:14 AM
Edited Jul 20, 2012 at 12:15 AM

First, you have to fetch the CountryRecord from the repo, instead of using the one from action parameter. So your action should look like either:

        public ActionResult DeletePOST(CountryRecord country)
        {           
            foreach (CountyRecord county in CountyRepo.Fetch(c => c.CountryRecord.Id==country.Id))
            {
                CountyRepo.Delete(county);
            }
           
            var countryRecord = CountryRepo.Get(country.Id);
            CountryRepo.Delete(countryRecord);

            OrchardServices.Notifier.Add(NotifyType.Information, T("Country '{0}' deleted successfully", countryRecord.CountryName));
            return RedirectToAction("Index");
        }

Or, should you just pass and Id to the DeletePost action method, instead of the full object - you actually only need an Id here (there is no point in using CountryRecord as model):

        public ActionResult DeletePOST(int countryId)
        {                    
            var countryRecord = CountryRepo.Get(countryId);
            if(countryRecord != null)
            {
                foreach (CountyRecord county in CountyRepo.Fetch(c => c.CountryRecord.Id==countryId))
                {
                    CountyRepo.Delete(county);
                }

                CountryRepo.Delete(countryRecord);
                OrchardServices.Notifier.Add(NotifyType.Information, T("Country '{0}' deleted successfully", countryRecord.CountryName));
            }
            return RedirectToAction("Index");
        }
Jul 20, 2012 at 8:15 AM

Thank you so much @pszmyd! Once again you have been a lifesaver. I knew it must have been a simple solution since it seems like such a common scenario.

Cheers

Developer
Jul 20, 2012 at 9:04 PM

@hofnarwillie You're welcome - glad I could help!