illegal attempt to associate a collection with two

2019-06-27 05:37发布

问题:

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?

回答1:

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)...



回答2:

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;
    }
}