NHibernate:System.Argument异常:已添加具有相同键的项

时间:2011-01-31 19:31:58

标签: nhibernate session fluent-nhibernate

我收到了一个难以重现的零星错误。我的第一个猜测是,我有一个泄漏的nhibernate会话,但是当我运行nhibernate profiler时,我没有看到太多与众不同的。

  • MVC 2.0
  • Fluent版本1.1.0.685
  • NHibernate版本2.1.2.4000
  

异常:System.ArgumentException:   具有相同键的项目已经存在   已被添加。

     

Stack Trace:at   System.Collections.Generic.Dictionary 2.Insert(TKey key, TValue value, Boolean add) at NHibernate.Util.ThreadSafeDictionary 2.添加(TKEY的   关键,TValue价值)at   NHibernate.SqlTypes.SqlTypeFactory.GetTypeWithLen [T](的Int32   length,TypeWithLenCreateDelegate   createDelegate)at   NHibernate.Type.EnumStringType..ctor(类型   enumClass,Int32 length)

我正在使用存储库模型。这是我的存储库类。

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
}

这是我的会话助手,改编自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;
    }
}

避免此问题的任何想法或建议?

4 个答案:

答案 0 :(得分:2)

问题是,SessionHelper不是线程安全的。它可能会构建几个会话工厂(这是一个不好的Singleton实现),这反过来可能会导致你看到的错误。

我建议使用SharpArchitecture作为指导。

答案 1 :(得分:1)

我遇到了同样的问题,“构建nhibernate配置时,已经添加了一个具有相同密钥的项目。”

我发生的事情是,两个线程以编程方式构建不同的配置,旨在同时连接到不同的数据库。

我在整个配置制造商周围加了一个锁,但问题就消失了。

所以我猜配置对象取决于一些内部全局状态,即假设配置本身是一个单独的(因为我猜它会是,如果它完全由文件驱动,而不是以编程方式构建)。

答案 2 :(得分:1)

我意识到这是一个老问题,但几天前我使用NHibernate 3.0发生了类似的错误。

对于可能偶然发现此问题的读者:这是旧版本NHibernate中已知线程安全问题的结果。这已在3.2版中修复,但旧版本将没有修复,可能会产生此问题。此错误条目描述了问题:https://nhibernate.jira.com/browse/NH-3271

答案 3 :(得分:1)

我走了@joel truher所写的路线。但是,我想把代码放在这里。

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

显然,您可以根据需要配置数据库。