Josh
Josh

Reputation: 16567

NHibernate: System.Argument Exception : An item with the same key has already been added

I'm getting a sporadic error that is difficult to reproduce. My first guess is that somehow I have a leaking nhibernate session, however when I ran the nhibernate profiler, I didn't see much out of the ordinary.

Exception: System.ArgumentException : An item with the same key has already been added.

Stack Trace: at System.Collections.Generic.Dictionary2.Insert(TKey key, TValue value, Boolean add) at NHibernate.Util.ThreadSafeDictionary2.Add(TKey key, TValue value) at NHibernate.SqlTypes.SqlTypeFactory.GetTypeWithLen[T](Int32 length, TypeWithLenCreateDelegate createDelegate) at NHibernate.Type.EnumStringType..ctor(Type enumClass, Int32 length)

I am using a repository model. Here's my repository class.

public sealed class Repository<T> : IRepository<T> where T : CoreObjectBase
{
    #region IRepository<T> Members

    private ISession Session
    {
        get
        {
            return new SessionHelper().GetSession();
        }
    }

    public IQueryable<T> GetAll()
    {
        return (from entity in Session.Linq<T>() select entity);
    }

    public T GetById(int id)
    {
        return Session.Get<T>(id);
    }

    public void Save(params T[] entities)
    {
        using (ITransaction tx = Session.BeginTransaction())
        {
            for (int x = 0; x < entities.Count(); x++)
            {
                var entity = entities[x];

                entity.Validate();

                Session.SaveOrUpdate(entities[x]);

                if (x == entities.Count() - 1 || (x != 0 && x % 20 == 0)) //20 is the batch size
                {
                    Session.Flush();
                    Session.Clear();
                }
            }
            tx.Commit();
        }
    }

    public void SaveWithDependence<K>(T entity, K dependant) where K : CoreObjectBase
    {
        entity.Validate();
        dependant.Validate();

        using (ITransaction tx = Session.BeginTransaction())
        {
            Session.SaveOrUpdate(entity);
            Session.SaveOrUpdate(dependant);
            tx.Commit();
        }
    }

    public void Save(T entity)
    {
        entity.Validate();

        using (ITransaction tx = Session.BeginTransaction())
        {
            Session.SaveOrUpdate(entity);
            tx.Commit();
        }
    }

    public void Delete(T entity)
    {
        using (ITransaction tx = Session.BeginTransaction())
        {
            Session.Delete(entity);
            tx.Commit();
        }
    }

    public T GetOne(QueryBase<T> query)
    {
        var result = query.SatisfyingElementFrom(Session.Linq<T>());

        return result;

        //return query.SatisfyingElementFrom(Session.Linq<T>());
    }

    public IQueryable<T> GetList(QueryBase<T> query)
    {
        return query.SatisfyingElementsFrom(Session.Linq<T>());
    }

    /// <summary>
    /// remove the sepcific object from level 1 cache so it can be refreshed from the database
    /// </summary>
    /// <param name="entity"></param>
    public void Evict(T entity)
    {
        Session.Evict(entity);
    }
    #endregion
}

And here is my session helper, adapted from this.

public sealed class SessionHelper
{
    private static ISessionFactory _sessionFactory;
    private static ISession _currentSession;

    public ISession GetSession()
    {
        ISessionFactory factory = getSessionFactory();
        ISession session = getExistingOrNewSession(factory);
        return session;
    }

    private ISessionFactory getSessionFactory()
    {
        if (_sessionFactory == null)
        {
            _sessionFactory = BuildSessionFactory();
        }

        return _sessionFactory;
    }

    private ISessionFactory BuildSessionFactory()
    {
        return Fluently.Configure().Database(
            FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2005
                .ConnectionString(c => c
                    .FromConnectionStringWithKey("MyDatabase"))
                    .AdoNetBatchSize(20))
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<SessionHelper>())
                .BuildSessionFactory();
    }

    private ISession getExistingOrNewSession(ISessionFactory factory)
    {
        if (HttpContext.Current != null)
        {
            ISession session = GetExistingWebSession();
            if (session == null)
            {
                session = openSessionAndAddToContext(factory);
            }
            else if (!session.IsOpen)
            {
                session = openSessionAndAddToContext(factory);
            }

            return session;
        }

        if (_currentSession == null)
        {
            _currentSession = factory.OpenSession();
        }
        else if (!_currentSession.IsOpen)
        {
            _currentSession = factory.OpenSession();
        }

        return _currentSession;
    }

    public ISession GetExistingWebSession()
    {
        return HttpContext.Current.Items[GetType().FullName] as ISession;
    }

    private ISession openSessionAndAddToContext(ISessionFactory factory)
    {
        ISession session = factory.OpenSession();
        HttpContext.Current.Items.Remove(GetType().FullName);
        HttpContext.Current.Items.Add(GetType().FullName, session);
        return session;
    }
}

Any ideas or suggestions to avoid this issue?

Upvotes: 7

Views: 5408

Answers (4)

ScubaSteve
ScubaSteve

Reputation: 8270

I went the route that @joel truher wrote about. But, I wanted to put the code here on how to do it.

public class NHibernateBootstrapper
{
    private static readonly object _sessionFactoryLock = new object();
    private static ISessionFactory _sessionFactory;

    public static ISessionFactory CreateThreadStaticSessionFactory(string connectionString, bool exportSchema)
    {
        lock (_sessionFactoryLock)
        {
            if (_sessionFactory == null)
            {
                _sessionFactory = Fluently.Configure()
                                          .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
                                          .AdoNetBatchSize(16))
                                          .CurrentSessionContext<ThreadStaticSessionContext>()
                                          .Mappings(m =>
                                          {
                                              m.FluentMappings.AddFromAssemblyOf<NHibernateBootstrapper>()
                                                  .Conventions.AddFromAssemblyOf<NHibernateBootstrapper>();
                                              m.HbmMappings.AddFromAssemblyOf<NHibernateBootstrapper>();
                                          })
                                          .ExposeConfiguration(cfg => BuildSchema(cfg, exportSchema))
                                          .BuildSessionFactory();
            }

            return _sessionFactory;
        }
    }
}

Obviously, you can configure your database however you'd like.

Upvotes: 1

xxbbcc
xxbbcc

Reputation: 17366

I realize that this is an old question but I had a similar error just a few days ago, using NHibernate 3.0.

For readers that may stumble upon this issue: this is the result of a known thread-safety problem in older versions of NHibernate. This was fixed in version 3.2 but older versions will not have the fix and may produce this problem. This bug entry describes the issue: https://nhibernate.jira.com/browse/NH-3271

Upvotes: 1

joel truher
joel truher

Reputation: 748

I ran into the same issue, "An item with the same key has already been added" while constructing the nhibernate configuration.

What was happening for me was that two threads were programmatically constructing different configurations, intended to connect to different databases, at the same time.

I added a lock around the entire configuration-maker, and the problem went away.

So I guess the configuration object depends on some internal global state, i.e. assumes that the configuration itself is a singleton (as i guess it would be, if it were totally file-driven, as opposed to programmatically constructed).

Upvotes: 1

Mauricio Scheffer
Mauricio Scheffer

Reputation: 99750

Problem is, that SessionHelper isn't thread-safe. It will potentially build several session factories (it's a bad implementation of Singleton), which in turn probably causes the error you're seeing.

I recommend using SharpArchitecture as guidance instead.

Upvotes: 2

Related Questions