I want to update M:N data but when I do that I have this exception:
Initializing[DataAccess.Model.Product#6]-Illegally attempted to associate a proxy with two open Sessions
I think it is something with my sesion code but cant figure it out can you help me?
Here is a code of update session
public void Update(T entity)
{
using (ITransaction transaction = Session.BeginTransaction())
{
Session.Update(entity);
transaction.Commit();
}
}
This is how the method looks like in controller
[HttpPost]
public ActionResult Add(int product)
{
Create();
Product productD = new ProductDao().GetById(product);
ProductsOfBag productsOfBag = new ProductsOfBag();
User user = new UserDao().GetByLogin(User.Identity.Name);
Bag bag = new BagDao().GetByUser(user);
bag.Price += productD.Price;
bag.PriceDph += productD.PriceDph;
bag.NumberOfItems++;
ProductsOfBagDao productsOfBagDao = new ProductsOfBagDao();
productsOfBag.IdBag = bag;
productsOfBag.IdProduct = productD;
productsOfBagDao.Create(productsOfBag);
IList<ProductsOfBag> products = productsOfBagDao.GetByBag(bag);
productD.Bags = products;
bag.Products = products;
BagDao bagDao = new BagDao();
bagDao.Update(bag);
return RedirectToAction("Index", "Home");
}
Product.hbm.xml
<class name="Product" table="Products" lazy="true">
<id name="Id" column="id_product">
<generator class="native" />
</id>
<property name="Name" column="name" />
<property name="PriceDph" column="priceDph" />
<property name="Price" column="price" />
<property name="ProductState" column="productState" />
<property name="Maker" column="maker" />
<property name="Description" column="description" />
<property name="ProductWaranty" column="productWaranty" />
<property name="Points" column="points" />
<many-to-one name="Category" column="id_category" foreign-key="id_category" />
<property name="ImageName" column="imageName" />
<bag name="Bags" lazy="true"
inverse="true" batch-size="25" cascade="all-delete-orphan">
<key column="id_product" />
<one-to-many class="ProductsOfBag" />
</bag>
</class>
Bag.hbm.xml
<class name="Bag" table="Bags" lazy="true">
<id name="Id" column="id_bag">
<generator class="native" />
</id>
<property name="Price" column="price" />
<property name="PriceDph" column="priceDph" />
<property name="NumberOfItems" column="numberOfItems" />
<many-to-one name="IdUser" column="id_User" foreign-key="id_User" />
<bag name="Products" lazy="true"
inverse="true" batch-size="25" cascade="all-delete-orphan">
<key column="id_bag" />
<one-to-many class="ProductsOfBag" />
</bag>
</class>
Product.cs
public class Product : IEntity
{
public virtual int Id { get; set; }
[Required(ErrorMessage = "Název produktu je vyžadován")]
public virtual string Name { get; set; }
private double _priceDph;
public virtual double PriceDph
{
get
{
_priceDph = Price + Price*0.21;
return _priceDph;
}
set { _priceDph = value; }
}
[Required(ErrorMessage = "Cena je vyžadována")]
[Range(0, 9000000, ErrorMessage = "Cena nemůže být záporná")]
public virtual int Price { get; set; }
public virtual string ProductState { get; set; }
[Required(ErrorMessage = "Výrobce je vyžadován")]
public virtual string Maker { get; set; }
[AllowHtml]
public virtual string Description { get; set; }
[Required(ErrorMessage = "Záruka je vyžadována")]
public virtual int ProductWaranty { get; set; }
[Required(ErrorMessage = "Počet bodů je vyžadován")]
private int _points;
public virtual int Points
{
get
{
_points = (int)PriceDph/10;
return _points;
}
set { _points = value; }
}
public virtual ProductCategory Category { get; set; }
public virtual string ImageName { get; set; }
public virtual IList<ProductsOfBag> Bags { get; set; }
}
Bag.cs
public class Bag :IEntity
{
public virtual int Id { get; set; }
public virtual double Price { get; set; }
public virtual double PriceDph { get; set; }
public virtual int NumberOfItems { get; set; }
public virtual User IdUser { get; set; }
public virtual IList<ProductsOfBag> Products { get; set; }
}
NHibernateHelper.cs
public class NHibernateHelper
{
private static ISessionFactory _factory;
public static ISession Session
{
get
{
if (_factory == null)
{
var cfg = new Configuration();
_factory =
cfg.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Hibernate.cfg.xml"))
.BuildSessionFactory();
}
return _factory.OpenSession();
}
}
}
There is an issue related to mapping of similar collection vs its different runtime instance(s). Better to discuss that over the code, so this is one entity
And this is the second one:
Both seems to be of a same type:
IList<ProductsOfBag>
, but they are in fact two different instances. That means that this piece of codeis ok, but next lines are real problem, the source of the exception
This is not allowed. Each object has its own collection. These collections are part of the NHibernate
ISession
(theproductD.Bags
andbag.Products
) - they/instances were created during the Load.So, we should be able to skipp, remove these 3 lines and just call:
If all DAO objects share the same ISession instance (see below), it should work now
ORIGINAL part
Also, when we use NHibernate with web applications, mostly
ASP.NET MVC
orWeb API
, we should think about unit of work being related to whole web request.To support that approach, we can use built in AOP filters, or delegation handlers. We can follow this comprehensive post:
NHibernate session management in ASP.NET Web API
mostly the part:
Session management action filter
(showing simplified code snippet)
Some similar stuff:
In case, that this is not the issue, session is already opened through the whole web request, we would get the exception
in case, that we kept in memory one object during two (or even more) sessions. And that is easy to (accidently) achieve with ASP.NET MVC feature:
We could be having an instance of loaded Entity... loaded in some POST Action / web request... being passed/redirected to some other Action. But that will effectively create new Web Request. And that will trigger the AOP filters... which will close the first session and open brand new for new Request.
To avoid this, we should strongly distinguish Actions for
They should not share anything. First should do all the UPDATEs, INSERTs and DELETEs - commit transaction... close the session. Only ID should be used for redirection. Later, we should, in a brand new session, just READ - properly committed and cleared stuff...