#nhibernate #session #transactions
#nhibernate #сессия #транзакции
Вопрос:
Я вижу некоторые странные ошибки NHibernate, которые время от времени возникают в нашей производственной среде, но не на наших тестовых серверах. Пройдут дни без ошибок, затем мы получим ошибку, которая выглядит как показано ниже, и это похоже на то, как если бы открылись шлюзы, и эти ошибки происходят повсюду в десятках разных частей нашего сайта. Единственный способ предотвратить их возникновение — сбросить пул приложений.
Мы открываем один сеанс и сопровождаем транзакцию для каждого поступающего HTTP-запроса. Он хранится в HttpContext.Current.Сбор элементов. Каждый из наших репозиториев расширяет базовый класс репозитория, который ссылается на ранее открытый сеанс. Вот модуль, который открывает и закрывает сеансы и транзакции:
public class NHibernateSessionModule : IHttpModule
{
#region Public Methods
/// <summary>
/// Initializes the http module by hooking up the open session call to the begin
/// request event and the close session call to the end request event.
/// </summary>
/// <param name="context">The context representing the current http request.</param>
public void Init(HttpApplication context)
{
context.BeginRequest = ApplicationBeginRequest;
context.EndRequest = ApplicationEndRequest;
context.Error = OnError;
}
/// <summary>
/// Disposes of the module.
/// </summary>
public void Dispose() { }
#endregion
#region Private Methods
/// <summary>
/// Fired when an error occurs during a request. Cleans up any open sessions and transactions.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
private static void OnError(object sender, EventArgs e)
{
var system = HttpContext.Current.Items["SystemSession"] as Lazy<ISession>;
if (system != null amp;amp; system.IsValueCreated amp;amp; system.Value != null)
RollbackSession(system.Value);
}
/// <summary>
/// Fired as a request begins. Opens a session and accompanying transaction. Also authenticates the
/// currently logged in user.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
private static void ApplicationBeginRequest(object sender, EventArgs e)
{
OpenSession("SystemSession", SessionFactoryProvider.SystemSessionFactory);
}
/// <summary>
/// Opens a new session.
/// </summary>
/// <param name="key">The key used to store the opened session in the current http context.</param>
/// <param name="sessionFactory">The factory used to open the session.</param>
private static void OpenSession(string key, ISessionFactory sessionFactory)
{
if (HttpContext.Current.Items[key] != null)
return;
HttpContext.Current.Items[key] = new Lazy<ISession>(() => {
var session = sessionFactory.OpenSession();
session.BeginTransaction();
return session;
}, true);
}
/// <summary>
/// Fired as a request ends. Closes the previously opened session and commits the session's transaction.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
private static void ApplicationEndRequest(object sender, EventArgs e)
{
var system = HttpContext.Current.Items["SystemSession"] as Lazy<ISession>;
if (system != null amp;amp; system.IsValueCreated amp;amp; system.Value != null)
CommitSession(system.Value);
}
/// <summary>
/// Commits a session's transaction.
/// </summary>
/// <param name="session">The session.</param>
private static void CommitSession(ISession session)
{
if (session == null)
return;
if (session.Transaction != null amp;amp; session.Transaction.IsActive)
{
var transaction = session.Transaction;
try
{
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
}
}
session.Close();
}
/// <summary>
/// Rolls back a session's transaction.
/// </summary>
/// <param name="session">The session.</param>
private static void RollbackSession(ISession session)
{
if (session == null)
return;
if (session.Transaction != null amp;amp; session.Transaction.IsActive)
session.Transaction.Rollback();
session.Close();
}
#endregion
}
Вот статический класс, используемый для создания экземпляров ISessionFactory:
public class SessionFactoryProvider
{
private static Configuration _configuration;
public static Configuration Configuration
{
get
{
return _configuration;
}
}
public static void Initialize()
{
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("Database")))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>())
.ExposeConfiguration(c => _configuration = c)
.BuildSessionFactory();
}
public static void Initialize(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
private static ISessionFactory _sessionFactory;
public static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
Initialize();
return _sessionFactory;
}
}
}
Вот базовый класс репозитория:
public class BaseRepository<T>
{
#region Properties
/// <summary>
/// Opens and returns a new session.
/// </summary>
private static ISession OpenSession
{
get
{
if (HttpContext.Current != null)
{
var lazySession = (Lazy<ISession>)HttpContext.Current.Items["SystemSession"];
if (lazySession != null)
return lazySession.Value;
}
TypeContainer.Get<ILog>().Warn("Unable to find session in http context");
throw new InvalidOperationException("The current HTTP context contains no system session.");
}
}
private ISession _session;
public ISession CurrentSession
{
get
{
if (_session == null || !_session.IsOpen || _session.Connection.State == ConnectionState.Closed)
_session = OpenSession;
return _session;
}
}
#endregion
}
Вот выборка некоторых ошибок, которые мы получаем:
CustomerID20_
at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at NHibernate.Driver.NHybridDataReader.GetOrdinal(String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String[] names, ISessionImplementor session, Object owner)
at NHibernate.Loader.Loader.GetKeyFromResultSet(Int32 i, IEntityPersister persister, Object id, IDataReader rs, ISessionImplementor session)
at NHibernate.Loader.Loader.GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
The server failed to resume the transaction. Desc:480000000f.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at NHibernate.AdoNet.AbstractBatcher.ExecuteReader(IDbCommand cmd)
at NHibernate.Loader.Loader.GetResultSet(IDbCommand st, Boolean autoDiscoverTypes, Boolean callable, RowSelection selection, ISessionImplementor session)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
CustomerID26_
at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at NHibernate.Driver.NHybridDataReader.GetOrdinal(String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String[] names, ISessionImplementor session, Object owner)
at NHibernate.Type.ManyToOneType.Hydrate(IDataReader rs, String[] names, ISessionImplementor session, Object owner)
at NHibernate.Type.ComponentType.Hydrate(IDataReader rs, String[] names, ISessionImplementor session, Object owner)
at NHibernate.Type.ComponentType.NullSafeGet(IDataReader rs, String[] names, ISessionImplementor session, Object owner)
at NHibernate.Loader.Loader.GetKeyFromResultSet(Int32 i, IEntityPersister persister, Object id, IDataReader rs, ISessionImplementor session)
at NHibernate.Loader.Loader.GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
Transaction not connected, or was disconnected
at NHibernate.Transaction.AdoTransaction.CheckNotZombied()
at NHibernate.Transaction.AdoTransaction.Rollback()
at ACC.Web.Modules.NHibernateSessionModule.CommitSession(ISession session)
at ACC.Web.Modules.NHibernateSessionModule.ApplicationEndRequest(Object sender, EventArgs e)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Booleanamp; completedSynchronously)
ID28_0_
at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at NHibernate.Driver.NHybridDataReader.GetOrdinal(String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String[] names, ISessionImplementor session, Object owner)
at NHibernate.Loader.Loader.GetKeyFromResultSet(Int32 i, IEntityPersister persister, Object id, IDataReader rs, ISessionImplementor session)
at NHibernate.Loader.Loader.GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
Transaction not successfully started
at NHibernate.Transaction.AdoTransaction.CheckBegun()
at NHibernate.Transaction.AdoTransaction.Rollback()
at ACC.Web.Modules.NHibernateSessionModule.CommitSession(ISession session)
at ACC.Web.Modules.NHibernateSessionModule.ApplicationEndRequest(Object sender, EventArgs e)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Booleanamp; completedSynchronously)
col_0_0_
at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at NHibernate.Driver.NHybridDataReader.GetOrdinal(String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String name)
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String[] names, ISessionImplementor session, Object owner)
at NHibernate.Hql.Ast.ANTLR.Loader.QueryLoader.GetResultColumnOrRow(Object[] row, IResultTransformer resultTransformer, IDataReader rs, ISessionImplementor session)
at NHibernate.Loader.Loader.GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
The server failed to resume the transaction. Desc:8040000001d.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at NHibernate.AdoNet.AbstractBatcher.ExecuteReader(IDbCommand cmd)
at NHibernate.Loader.Loader.GetResultSet(IDbCommand st, Boolean autoDiscoverTypes, Boolean callable, RowSelection selection, ISessionImplementor session)
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
Многие из вышеперечисленных ошибок имеют внутренние сообщения об исключениях, такие как:
System.InvalidOperationException: Invalid attempt to call Read when reader is closed.
System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
System.Data.SqlClient.SqlException: New request is not allowed to start because it should come with valid transaction descriptor.
System.IndexOutOfRangeException: ID28_0_
NHibernate.AssertionFailure: possible non-threadsafe access to the session
System.InvalidOperationException: ExecuteReader requires an open and available Connection. The connection's current state is open.
Многие из этих ошибок выглядят так, как будто сеанс открывается, осуществляется доступ или закрывается через несколько потоков, но мы не создаем никаких других потоков, которые обращаются к базе данных в любом месте приложения.
Просмотр журналов профилировщика NHibernate показывает нам, что транзакции открываются и фиксируются / откатываются в каждом случае.
Мы пытались решить эту проблему в течение некоторого времени, и у нас закончились идеи. Кто-нибудь сталкивался с этой проблемой раньше? Есть идеи?
Спасибо!
Крис
Комментарии:
1. вероятно, вам следует повторно запустить это пойманное исключение вместо того, чтобы пытаться выполнить откат и игнорировать его…
2. Привет, Крис, мы наблюдаем подобное странное поведение с NHibernate. Все работает с перебоями, а затем мы получаем ошибки, аналогичные тем, о которых вы сообщили. Вы когда-нибудь решали эту проблему? Спасибо.
Ответ №1:
Мы не используем NHibernate, поэтому я не уверен, насколько это применимо, но мы ежедневно получали тонны ошибок GetOrdinal. Datareaders и datatables возвращались с неожиданными данными (т. Е. Из совершенно другого запроса, чем мы ожидали). Недавно я обнаружил аргумент строки подключения «Enlist», и после добавления «Enlist = False» ко всем нашим строкам подключения мы не выдали ни одной ошибки GetOrdinal.
Я считаю, что это изменение, вероятно, исправит множество подобных проблем, о которых люди сообщали здесь и в других местах.
Майк