【问题标题】:NHibernate: System.Argument Exception : An item with the same key has already been addedNHibernate:System.Argument 异常:已添加具有相同键的项目
【发布时间】:2011-06-18 19:34:28
【问题描述】:

我遇到了一个难以重现的零星错误。我的第一个猜测是不知何故我有一个泄漏的 nhibernate 会话,但是当我运行 nhibernate profiler 时,我并没有看到太多异常。

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

异常:System.ArgumentException: 具有相同密钥的项目已经 已添加。

堆栈跟踪:在 System.Collections.Generic.Dictionary2.Insert(TKey key, TValue value, Boolean add) at NHibernate.Util.ThreadSafeDictionary2.Add(TKey 键,TValue 值)在 NHibernate.SqlTypes.SqlTypeFactory.GetTypeWithLen[T](Int32 长度,TypeWithLenCreateDelegate createDelegate) 在 NHibernate.Type.EnumStringType..ctor(类型 枚举类,Int32 长度)

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

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

有什么想法或建议可以避免这个问题?

【问题讨论】:

  • 乔希,你让你的 SessionHelper 线程安全吗?

标签: nhibernate session fluent-nhibernate


【解决方案1】:

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

我建议改用SharpArchitecture 作为指导。

【讨论】:

  • 我真的不想在我的项目中导入另一个框架。我的设置是这样的,我的 web 项目不知道 nhibernate 或引用 nhibernate 的库,我想保持这种状态。 MVC 应用程序 -> 服务层 -> 存储库层 -> SessionHelper。它是故意这样设计的,因为 Windows 应用程序通过同样使用服务层的 Web 服务进行连接。
  • @Josh:您不必购买整个框架,您只需浏览代码并使用它的想法即可。
  • SharpArchitecture 不见了
【解决方案2】:

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

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

我在整个配置生成器周围加了一个锁,问题就消失了。

所以我猜配置对象依赖于一些内部全局状态,即假设配置本身是一个单例(我猜它会是,如果它完全是文件驱动的,而不是编程构造的)。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      我走了@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;
              }
          }
      }
      

      显然,您可以随意配置数据库。

      【讨论】:

        猜你喜欢
        • 2014-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多