4

I have this exception "illegal attempt to associate a collection with two open sessions", it raises every time I save entity contains collection of children. I google it. I found that I opened two or more sessions when calling save, but I'm sure that I'm using only one session. Where I did wrong? How can I solve this problemn? Note: I'm using MVC4, and fluent NHibernate.

Entities:

public class Employee : EntityBase<int>
{
    public Employee()
        : base()
    {
        Phones = new List<Phone>();
    }

    public Employee(int id) : this() { Id = id; }
    [Browsable(false)]
    public override ApprovalBase Approval
    {
        get;
        set;
    }

    public virtual string Name { get; set; }
    public virtual string Job { get; set; }

    [Browsable(false)]
    public virtual IList<Phone> Phones { get; set; }
}
public class Phone : EntityBase<int>
{
    public Phone()
        : base()
    {
    }
    public Phone(int id) : this() { Id = id; }
    public override ApprovalBase Approval
    {
        get;
        set;
    }

    public virtual string PhoneNumber { get; set; }
    public virtual string PhoneType { get; set; }
    public virtual int EmployeeId { get; set; }

    public virtual Employee Employee { get; set; }
}

Mapping:

public sealed class EmployeeMap : ClassMap<Employee>
{
    public EmployeeMap()
    {
        Table("dbo.Employee");

        Id(x => x.Id).Column("EmployeeId");
        Map(x => x.Name);
        Map(x => x.Job);
        HasMany(x => x.Phones).KeyColumn("EmployeeId").Table("dbo.Phone").Cascade.All().Inverse();
    }
}
public sealed class PhoneMap : ClassMap<Phone>
{
    public PhoneMap()
    {
        Table("dbo.Phone");
        Id(x => x.Id).Column("PhoneId");
        Map(x => x.PhoneNumber);
        Map(x => x.PhoneType);
        Map(x => x.EmployeeId);
        References(x => x.Employee).Column("EmployeeId")
            .Not.Update()
            .Not.Insert();
    }
}

Repository:

public abstract class RepositoryBase<TEntity, TIdentity>
    : IRepository<TEntity, TIdentity>
    where TEntity : EntityBase<TIdentity>
    where TIdentity : IComparable
{
    private readonly IPersistor<TEntity, TIdentity> persistor; //contains the session to operate with the database
    public IPersistor<TEntity, TIdentity> Persistor { get { return persistor; } }

    private readonly IFinder<TEntity, TIdentity> finder;
    public IFinder<TEntity, TIdentity> Finder { get { return finder; } }

    private RepositoryBase() { }

    public RepositoryBase(
        IPersistor<TEntity, TIdentity> persistor,
        IFinder<TEntity, TIdentity> finder)
    {
        this.persistor = persistor;
        this.finder = finder;
        this.finder.DataSource = Query();
    }

    // Get entity by ID
    public virtual TEntity Get(TIdentity id)
    {
        return persistor.Get(id);
    }

    /// <summary>
    /// Validate and Save the entity. If the validation failed, will not save the entity,
    /// but returns back list of error messages.
    /// </summary>
    public virtual IList<String> Save(TEntity entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        IList<String> errors = entity.Validate();

        if (errors.Count == 0)
        {
            persistor.Save(entity);
        }

        return errors;
    }

    // Delete entity from persistance repository
    public virtual void Delete(TIdentity id)
    {
        persistor.Delete(id);
    }

    /// Gets IQueryable which we use from the concrete
    /// implementation of repository to implement our 
    /// query methods (FindBy).
    protected IQueryable<TEntity> Query()
    {
        return persistor.Query();
    }

    public IList<TEntity> GetAll()
    {
        return persistor.Query().ToList();
    }
}

public class EmployeeRepository : RepositoryBase<Employee, int>, IEmployeeRepository
{
    public EmployeeRepository(
        IPersistor<Employee, int> persistor,
        IEmployeeFinder entityFinder)
        : base(persistor, entityFinder) { }

    public IEmployeeFinder Find
    {
        get { return (IEmployeeFinder)Finder; }
    }
}
public class PhoneRepository : RepositoryBase<Phone, int>, IPhoneRepository
{
    public PhoneRepository(
        IPersistor<Phone, int> persistor,
        IPhoneFinder entityFinder)
        : base(persistor, entityFinder) { }

    public IPhoneFinder Find
    {
        get { return (IPhoneFinder)Finder; }
    }
}

After I fill all the information of the Employee and add collection of phones, when I press save, the information haven't been saved in the database. After some debugging, I found that when my program reaches to "Session.SaveOrUpdate(entity);" the exception above appeared. How to solve this issue?

Rasool Ahmed
  • 103
  • 2
  • 9
  • 1
    I guess: You do have two guys in play for handling your repository: Mr. `persistor` and Mr. `finder`. They both do have an **instance** of `ISession`, its own. If `finder` does succeed and finds something... e.g. *Phones* ... the entity *Employee* assing them and tries to Save ... but: `Phones` are inside a different session *(instance)* then `Employee`. I guess, but in fact I am sure ... ;) – Radim Köhler Jul 31 '14 at 15:58
  • But, I looked in the code and I found I'm using one session, because my controller from MVC4 initiate one session at the constructor, and use this session in the repository of employee and the employee have phones and the relation between them is one to many (see the mapper). Where did I wrong? – Rasool Ahmed Jul 31 '14 at 17:42
  • The exception is really clear, and we both understand it. Most likely (surely) phones were loaded with different session, the which is the "current" - processing the Employee. If the session is really common, then the only other opiton is that you are using "Redirect". If yes, you could load an object in one controller life time - put it into *Temp* - call Redirect to other controller - and now there is new session and older object associated with older session (closed one). Solution: Assure, that all the stuff you do is at ONE place. think about your current stuff,and you will surely get it;) – Radim Köhler Aug 01 '14 at 03:25
  • Thanx Radim Kohler, that was very helpful. I will post the solution soon. – Rasool Ahmed Aug 01 '14 at 04:43

2 Answers2

2

Also, for completeness, there are usually two types of issues:

  1. Commonly related to improperly managed DAO objects with their own ISession creation:

    As in the example defined above, there could be two objects in play to work with the repository: (a) persistor and (b) finder.

    They each have an instance of ISession. If finder succeeds and finds something (e.g. Phones), the entity Employee asking for them tries to Save, but Phones are inside a different session than Employee.

  2. Very often related to ASP.NET MVC and its Redirect() action results:

    Most likely Phones were loaded by a different session. Not by the "current" that processes the Employee.

    So the most suspect is the call Redirect(). If yes, what happens is that we load an object in one controller life time - put it into Temp dictionary - call Redirect to other controller - and now there is a new session as well as an older object associated with an older, closed session.

Solution: Be sure, that all the DAO handling is part of one ISession scope. Do not transfer any data among sessions (nor among controller redirections)...

Michael
  • 8,362
  • 6
  • 61
  • 88
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • 1
    That's was the problem with my application. I separated "EmployeeController" and "PhoneController" and using sessin["Phones"] between them to manage one to many relation in MVC4. I solve it by makingPhone controller functions inside EmployeeContoller, and the problem solved :) – Rasool Ahmed Aug 01 '14 at 05:16
1

I solved it with lock statements in every methods where there was static variables being used, because the root of the problem was syncronization related.

Here is a simple example to ilustrate my solution:

private static int sharedVariable;
private static object _syncronizationObject = new Object();

public void MethodThatUsesStaticVariable(int newValue)
{
    // This lock prevents concurrency problems, and this is what solved the issue for me.
    lock(_syncronizationObject)
    {
        sharedVariable = newValue;
    }
}
Ulysses Alves
  • 2,297
  • 3
  • 24
  • 34