【问题标题】:nhibernate 3.2 thread_static issuenhibernate 3.2 thread_static 问题
【发布时间】:2011-11-15 20:03:05
【问题描述】:

我遇到了 NHibernate 3.2 的问题。

我正在将我们在 Java 应用程序中使用的解决方案移植到 c# 4.0 应用程序。 我们想要创建一个简单的机制,通过 NHibernate SessionFactory 处理会话和事务,让工作单元初学者实例化事务,然后被所有参与者方法使用,甚至不知道它们是一部分更大的工作单元。但是如果你直接调用这些子方法,它们会自己处理事务。

these question 中,我更好地解释了我们的方法。 我们首先在 Java 世界中做到了,它工作得很好。 现在我正在使用 NHibernate 3.2 将相同的方法移植到 c# 4.0。

将处理我所有会话和事务的类是 OperationManager(你可以想到 UnitOfWorkManager):

public class OperationManager : IDisposable
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    ITransaction tx = null;
    ISession session = null;
    bool isInternalTransaction = false;

    public ISession BeginOperation()
    {
        logger.Debug("Thread : " + System.Threading.Thread.CurrentThread.ManagedThreadId);
        session = PersistenceManager.Istance.GetSession();
        if (session.Transaction.IsActive)
        {
            isInternalTransaction = false;
            tx = session.Transaction;                               
            logger.Debug(GetCallerClassDotMethod() + " is binding to transaction " + tx.GetHashCode());
        }
        else
        {
            isInternalTransaction = true;
            tx = session.Transaction;
            tx.Begin(); 
            logger.Debug("Transaction " + tx.GetHashCode() + " created by " + GetCallerClassDotMethod());
        }
        logger.Debug("Session hash " + session.GetHashCode());
        return session;
    }

    private String GetCallerClassDotMethod() {
        // needed to get the Business Logic method calling the public methods
        var st = new StackTrace();
        var sf = st.GetFrame(2);
        var methodReference = sf.GetMethod().Name;
        var classReference = sf.GetMethod().DeclaringType.FullName;
        return string.Concat(classReference, ".", methodReference);
    }

    public void CommitOperation()
    {
        if (isInternalTransaction)
        {                
            tx.Commit();
            logger.Debug(GetCallerClassDotMethod() + " is committing transaction " + tx.GetHashCode());
        }
    }

    public void RollbackOperation()
    {
        if (isInternalTransaction)
        {
            tx.Rollback();                
            logger.Debug(GetCallerClassDotMethod() + " is rolling back transaction " + tx.GetHashCode());                
        }
    }

    public void Dispose()
    {
        tx.Dispose();
        session.Dispose();
    }
}

这是我的 PersistenceManager

internal class PersistenceManager : IDisposable
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
    private static PersistenceManager _istance;
    private ISessionFactory _SessionFactory;
    private static Object _lock = new Object();

    public static PersistenceManager Istance
    {
        get
        {
            lock (_lock)
            {
                if (_istance == null)
                {
                    _istance = new PersistenceManager();
                    logger.Info("New PersistenceManager instance created");
                }
                return _istance;
            }
        }
    }

    private PersistenceManager()
    {
        // Initialize
        Configuration cfg = new Configuration();
        cfg.Configure(ConfigurationManager.ConfigurationManager.Istance.hibernateConfiguration);
        cfg.SetProperty("connection.connection_string", ConfigurationManager.ConfigurationManager.Istance.connectionString);

        /* Note: The AddAssembly() method requires that mappings be 
         * contained in hbm.xml files whose BuildAction properties 
         * are set to ‘Embedded Resource’. */

        // Add class mappings to configuration object
        System.Reflection.Assembly thisAssembly = typeof(PersistenceManager).Assembly;
        cfg.AddAssembly(thisAssembly);            

        // Create session factory from configuration object
        _SessionFactory = cfg.BuildSessionFactory();
    }



    public void Dispose()
    {
        _SessionFactory.Dispose();
    }


    /// <summary>
    /// Close this Persistence Manager and release all resources (connection pools, etc). It is the responsibility of the application to ensure that there are no open Sessions before calling Close().
    /// </summary>
    public void Close()
    {
        _SessionFactory.Close();
    }


    public ISession GetSession()
    {
        return _SessionFactory.OpenSession();
    }

}

现在,每当我需要访问数据库时,我都会使用 OperationManager 实例来获取当前会话(和当前事务)并将其用于我的所有需要​​。 这里有一个例子:

public IList<Agency> getAllAgencies()
    {
        using (var om = new OperationManager())
        {
            try
            {
                om.BeginOperation();
                var result = base.Load<Agency>().ToList();
                om.CommitOperation();
                return result;
            }
            catch (Exception ex)
            {
                om.RollbackOperation();
                throw ex;
            }
        }
    }

在我的基类中

protected IQueryable<T> Load<T>() where T : Model.ModelEntity
    {
        using (var om = new OperationManager())
        {
            try
            {
                var session = om.BeginOperation();
                var entities = session.Query<T>();
                om.CommitOperation();
                return entities;
            }
            catch (Exception ex)
            {
                om.RollbackOperation();
                throw new Exception(msg, ex);
            }
        }
    }

问题在于,即使我使用 &lt;property name="current_session_context_class"&gt;thread_static&lt;/property&gt; 将 NHibernate 会话工厂配置为在每个线程模型上工作,对 OperationManager.beginOperation() 的两次调用都会返回不同的会话,因此具有不同的事务。

谁能告诉我为什么会这样?

已编辑: 按照 Fredy Treboux 的建议,我尝试使用 NHibernate 的 CurrentSessionContext 静态对象来实现一种创建新会话或仅获取当前会话的机制。 太糟糕了,这仍然不起作用。 清理代码后,避免与事务、会话、工作单元等相关的所有内容,我编写了一个非常简单的类,我发现使用

<property name="current_session_context_class">thread_static</property> 

给我一​​个关于 sql server 2008 db 的问题。 在经典的 SessionFactory.OpenSession() 方法上使用该上下文类,然后加载一些数据,我收到以下错误:

System.Data.SqlClient.SqlException: A transport-level error has occurred when receiving results from the server. (provider: Shared Memory Provider, error: 0 - Invalid Handle.)

知道为什么会这样吗?

【问题讨论】:

标签: c# nhibernate design-patterns


【解决方案1】:

您每次都在调用 SessionFactory.OpenSession()。 这将打开并返回一个新会话,而不考虑其他所有内容。

尽管我不会急于推荐这种方法,但要使其发挥作用,您需要对 PersistenceManager 进行某种引用计数,以了解何时打开新会话以及何时释放它。

current_session_context_class 不会影响这一点,因为它只控制 SessionFactory.GetCurrentSession() 返回的内容,并且无论如何都必须通过 Bind/Unbind 手动操作 ThreadStaticSessionContext (thread_static)。

我会推荐的方法

嗯,有两件事。

首先,我喜欢定义一个特定的层来处理会话/事务。 我想说的是,如果我有方法 A 和 B,并且 A 可以使用 B,但 B 也可以从外部使用,我希望有一个方法 C 来定义 B 的会话/事务边界。

因此,A 和 C 是公开可用的方法,并且都使用 B(即使在 C 中您几乎直接调用 B)。

第二件事是,您最好使用已经可用的会话上下文管理器,就像您可以在许多基于 NHibernate 的库中找到的那样(例如http://code.google.com/p/unhaddins/),或者即使您决定继续实现自己的,尝试使其适合 NHibernate 中可用的 SessionContext 机制,因此您将能够调用 SessionFactory.GetCurrentSession() 来获取会话并与其他执行相同操作的上下文/方法兼容。

【讨论】:

  • 请问您推荐什么方法?我正在努力寻找最好的方法,所以任何建议都表示赞赏
  • 是的……主要是。但我想让业务层不知道是否在 Web 应用程序中使用。例如,我还将在某些 Windows 服务中使用它。所以我想避免著名的 session-per-request 模式。无论如何,按照你的建议,我想出了另一个与 thread_static 方法有关的错误。编辑原始问题
  • 您不需要避免每个请求的会话来使业务层不可知。这正是使用 CurrentSessionContext 和 SessionFactory.GetCurrentSession() 的重点。您可以将 ISessionFactory 提供给您的业务对象,他们需要做的就是调用 SessionFactory.GetCurrentSession() 来获取会话。然后,您只需根据情况配置适当的上下文,这将在您的业务层之外,而在 Web 中,它很可能是每个请求的会话。对不起,不知道你的新错误,你确定它与thread_static有关吗?
  • 今天我没有更多与 thread_static 相关的问题。所以我已经能够应用你的建议。现在一切正常。我不知道昨天发生了什么,但 NH 和 SQLServer 之间一定有问题。重启机器一切正常。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 2011-08-24
  • 1970-01-01
  • 2010-11-17
  • 2011-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多