From: <fab...@us...> - 2009-05-24 06:13:44
|
Revision: 4373 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=4373&view=rev Author: fabiomaulo Date: 2009-05-24 06:13:38 +0000 (Sun, 24 May 2009) Log Message: ----------- Fix NH-1553 Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Exceptions/ADOExceptionHelper.cs trunk/nhibernate/src/NHibernate/Exceptions/ISQLExceptionConverter.cs trunk/nhibernate/src/NHibernate/NHibernate.csproj trunk/nhibernate/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj Added Paths: ----------- trunk/nhibernate/src/NHibernate/Exceptions/AdoExceptionContextInfo.cs trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Mappings.hbm.xml trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Person.cs trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SQLUpdateConflictToStaleStateExceptionConverter.cs trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SnapshotIsolationUpdateConflictTest.cs Modified: trunk/nhibernate/src/NHibernate/Exceptions/ADOExceptionHelper.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Exceptions/ADOExceptionHelper.cs 2009-05-24 05:24:03 UTC (rev 4372) +++ trunk/nhibernate/src/NHibernate/Exceptions/ADOExceptionHelper.cs 2009-05-24 06:13:38 UTC (rev 4373) @@ -12,8 +12,20 @@ { public const string SQLNotAvailable = "SQL not available"; + public static Exception Convert(ISQLExceptionConverter converter, AdoExceptionContextInfo exceptionContextInfo) + { + if(exceptionContextInfo == null) + { + throw new AssertionFailure("The argument exceptionContextInfo is null."); + } + var sql = TryGetActualSqlQuery(exceptionContextInfo.SqlException, exceptionContextInfo.Sql); + ADOExceptionReporter.LogExceptions(exceptionContextInfo.SqlException, + ExtendMessage(exceptionContextInfo.Message, sql, null, null)); + return converter.Convert(exceptionContextInfo); + } + /// <summary> - /// Converts the given SQLException into NHibernate's ADOException hierarchy, as well as performing + /// Converts the given SQLException into Exception hierarchy, as well as performing /// appropriate logging. /// </summary> /// <param name="converter">The converter to use.</param> @@ -24,14 +36,11 @@ public static Exception Convert(ISQLExceptionConverter converter, Exception sqlException, string message, SqlString sql) { - sql = TryGetActualSqlQuery(sqlException, sql); - ADOExceptionReporter.LogExceptions(sqlException, ExtendMessage(message, sql, null, null)); - return - converter.Convert(new AdoExceptionContextInfo {SqlException = sqlException, Message = message, Sql = sql.ToString()}); + return Convert(converter, new AdoExceptionContextInfo {SqlException = sqlException, Message = message, Sql = sql.ToString()}); } /// <summary> - /// Converts the given SQLException into NHibernate's ADOException hierarchy, as well as performing + /// Converts the given SQLException into Exception hierarchy, as well as performing /// appropriate logging. /// </summary> /// <param name="converter">The converter to use.</param> @@ -40,16 +49,15 @@ /// <returns> The converted <see cref="ADOException"/>.</returns> public static Exception Convert(ISQLExceptionConverter converter, Exception sqlException, string message) { - var sql = new SqlString(SQLNotAvailable); - sql = TryGetActualSqlQuery(sqlException, sql); - return Convert(converter, sqlException, message, sql); + var sql = TryGetActualSqlQuery(sqlException, SQLNotAvailable); + return Convert(converter, new AdoExceptionContextInfo {SqlException = sqlException, Message = message, Sql = sql}); } public static ADOException Convert(ISQLExceptionConverter converter, Exception sqle, string message, SqlString sql, object[] parameterValues, IDictionary<string, TypedValue> namedParameters) { sql = TryGetActualSqlQuery(sqle, sql); - string extendMessage = ExtendMessage(message, sql, parameterValues, namedParameters); + string extendMessage = ExtendMessage(message, sql.ToString(), parameterValues, namedParameters); ADOExceptionReporter.LogExceptions(sqle, extendMessage); return new ADOException(extendMessage, sqle, sql.ToString()); } @@ -69,7 +77,7 @@ return result; } - public static string ExtendMessage(string message, SqlString sql, object[] parameterValues, + public static string ExtendMessage(string message, string sql, object[] parameterValues, IDictionary<string, TypedValue> namedParameters) { var sb = new StringBuilder(); @@ -105,5 +113,11 @@ } return sql; } + + public static string TryGetActualSqlQuery(Exception sqle, string sql) + { + var query = (string)sqle.Data["actual-sql-query"]; + return query ?? sql; + } } } \ No newline at end of file Added: trunk/nhibernate/src/NHibernate/Exceptions/AdoExceptionContextInfo.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Exceptions/AdoExceptionContextInfo.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Exceptions/AdoExceptionContextInfo.cs 2009-05-24 06:13:38 UTC (rev 4373) @@ -0,0 +1,40 @@ +using System; + +namespace NHibernate.Exceptions +{ + /// <summary> + /// Collect data of an <see cref="ADOException"/> to be converted. + /// </summary> + [Serializable] + public class AdoExceptionContextInfo + { + // This class was introduced, in NH, to allow less intrusive possible extensions + // of information given to the ISQLExceptionConverter + // (extensions of a class instead succesive changes of a method) + + /// <summary> + /// The <see cref="System.Data.Common.DbException"/> to be converted. + /// </summary> + public Exception SqlException { get; set; } + + /// <summary> + /// An optional error message. + /// </summary> + public string Message { get; set; } + + /// <summary> + /// The SQL that generate the exception + /// </summary> + public string Sql { get; set; } + + /// <summary> + /// Optional EntityName where available in the original exception context. + /// </summary> + public string EntityName { get; set; } + + /// <summary> + /// Optional EntityId where available in the original exception context. + /// </summary> + public object EntityId { get; set; } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Exceptions/ISQLExceptionConverter.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Exceptions/ISQLExceptionConverter.cs 2009-05-24 05:24:03 UTC (rev 4372) +++ trunk/nhibernate/src/NHibernate/Exceptions/ISQLExceptionConverter.cs 2009-05-24 06:13:38 UTC (rev 4373) @@ -2,31 +2,6 @@ namespace NHibernate.Exceptions { - /// <summary> - /// Collect data of an <see cref="ADOException"/> to be converted. - /// </summary> - public class AdoExceptionContextInfo - { - // This class was introduced, in NH, to allow less intrusive possible extensions - // of information given to the ISQLExceptionConverter - // (extensions of a class instead succesive changes of a method) - - /// <summary> - /// The <see cref="System.Data.Common.DbException"/> to be converted. - /// </summary> - public Exception SqlException { get; set; } - - /// <summary> - /// An optional error message. - /// </summary> - public string Message { get; set; } - - /// <summary> - /// The SQL that generate the exception - /// </summary> - public string Sql { get; set; } - } - /// <summary> /// Defines a contract for implementations that know how to convert a <see cref="System.Data.Common.DbException"/> /// into NHibernate's <see cref="ADOException"/> hierarchy. Modified: trunk/nhibernate/src/NHibernate/NHibernate.csproj =================================================================== --- trunk/nhibernate/src/NHibernate/NHibernate.csproj 2009-05-24 05:24:03 UTC (rev 4372) +++ trunk/nhibernate/src/NHibernate/NHibernate.csproj 2009-05-24 06:13:38 UTC (rev 4373) @@ -478,6 +478,7 @@ <Compile Include="EntityModeEqualityComparer.cs" /> <Compile Include="Event\AbstractPreDatabaseOperationEvent.cs" /> <Compile Include="Event\IDestructible.cs" /> + <Compile Include="Exceptions\AdoExceptionContextInfo.cs" /> <Compile Include="Exceptions\ReflectionBasedSqlStateExtracter.cs" /> <Compile Include="Exceptions\SqlStateExtracter.cs" /> <Compile Include="Exceptions\TemplatedViolatedConstraintNameExtracter.cs" /> Modified: trunk/nhibernate/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs 2009-05-24 05:24:03 UTC (rev 4372) +++ trunk/nhibernate/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs 2009-05-24 06:13:38 UTC (rev 4373) @@ -2727,7 +2727,15 @@ } catch (DbException sqle) { - throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle, "could not update: " + MessageHelper.InfoString(this, id, Factory), sql.Text); + var exceptionContext = new AdoExceptionContextInfo + { + SqlException = sqle, + Message = "could not update: " + MessageHelper.InfoString(this, id, Factory), + Sql = sql.Text.ToString(), + EntityName = EntityName, + EntityId = id + }; + throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, exceptionContext); } } Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Mappings.hbm.xml =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Mappings.hbm.xml (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Mappings.hbm.xml 2009-05-24 06:13:38 UTC (rev 4373) @@ -0,0 +1,19 @@ +<?xml version="1.0" ?> +<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" + assembly="NHibernate.Test" + namespace="NHibernate.Test.NHSpecificTest.NH1553.MsSQL"> + + + <class name="Person"> + <id name="Id" column="Id" type="long"> + <generator class="identity" /> + </id> + + <version name="Version" column="Version" type="long" /> + + <property name="IdentificationNumber" column="IdentificationNumber" type="long" /> + + </class> + + +</hibernate-mapping> Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Person.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Person.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/Person.cs 2009-05-24 06:13:38 UTC (rev 4373) @@ -0,0 +1,14 @@ +namespace NHibernate.Test.NHSpecificTest.NH1553.MsSQL +{ + /// <summary> + /// A simple entity for the test. + /// </summary> + public class Person + { + public virtual long Id { get; set; } + + public virtual long Version { get; set; } + + public virtual long IdentificationNumber { get; set; } + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SQLUpdateConflictToStaleStateExceptionConverter.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SQLUpdateConflictToStaleStateExceptionConverter.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SQLUpdateConflictToStaleStateExceptionConverter.cs 2009-05-24 06:13:38 UTC (rev 4373) @@ -0,0 +1,23 @@ +using System; +using System.Data.SqlClient; +using NHibernate.Exceptions; + +namespace NHibernate.Test.NHSpecificTest.NH1553.MsSQL +{ + /// <summary> + /// Exception converter to convert SQL Snapshot isolation update conflict to + /// StaleObjectStateException + /// </summary> + public class SQLUpdateConflictToStaleStateExceptionConverter : ISQLExceptionConverter + { + public Exception Convert(AdoExceptionContextInfo exInfo) + { + var sqle = ADOExceptionHelper.ExtractDbException(exInfo.SqlException) as SqlException; + if ((sqle != null) && (sqle.Number == 3960)) + { + return new StaleObjectStateException(exInfo.EntityName, exInfo.EntityId); + } + return SQLStateConverter.HandledNonSpecificException(exInfo.SqlException, exInfo.Message, exInfo.Sql); + } + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SnapshotIsolationUpdateConflictTest.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SnapshotIsolationUpdateConflictTest.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH1553/MsSQL/SnapshotIsolationUpdateConflictTest.cs 2009-05-24 06:13:38 UTC (rev 4373) @@ -0,0 +1,180 @@ +using System.Data; +using NHibernate.Cfg; +using NHibernate.Dialect; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH1553.MsSQL +{ + /// <summary> + /// Test fixture for NH1553, which checks update conflict detection together with snapshot isolation transaction isolation level. + /// </summary> + [TestFixture] + public class SnapshotIsolationUpdateConflictTest : BugTestCase + { + private Person person; + + public override string BugNumber + { + get { return "NH1553.MsSQL"; } + } + + private ITransaction BeginTransaction(ISession session) + { + return session.BeginTransaction(IsolationLevel.Snapshot); + } + + private Person LoadPerson() + { + using (ISession session = OpenSession()) + { + using (ITransaction tr = BeginTransaction(session)) + { + var p = session.Get<Person>(person.Id); + tr.Commit(); + return p; + } + } + } + + private void SavePerson(Person p) + { + using (ISession session = OpenSession()) + { + using (ITransaction tr = BeginTransaction(session)) + { + session.SaveOrUpdate(p); + session.Flush(); + tr.Commit(); + } + } + } + + /// <summary> + /// Tests, that NHibernate detects the update conflict and returns a StaleObjectStateException as expected. + /// This behaviour is part of the standard NHibernate feature set. + /// </summary> + [Test] + public void UpdateConflictDetectedByNH() + { + Person p1 = LoadPerson(); + Person p2 = LoadPerson(); + + p1.IdentificationNumber++; + p2.IdentificationNumber += 2; + + SavePerson(p1); + Assert.AreEqual(person.Version + 1, p1.Version); + try + { + SavePerson(p2); + Assert.Fail("Expecting stale object state exception"); + } + catch (StaleObjectStateException sose) + { + Assert.AreEqual(typeof (Person).FullName, sose.EntityName); + Assert.AreEqual(p2.Id, sose.Identifier); + // as expected. + } + } + + /// <summary> + /// Tests, that the extension provided wraps the returned SQL Exception inside a StaleObjectStateException, + /// if the SQL Server detects an update conflict in snapshot isolation. + /// </summary> + [Test] + public void UpdateConflictDetectedBySQLServer() + { + Person p1 = LoadPerson(); + + p1.IdentificationNumber++; + + using (ISession session1 = OpenSession()) + { + using (ITransaction tr1 = BeginTransaction(session1)) + { + session1.SaveOrUpdate(p1); + session1.Flush(); + + using (ISession session2 = OpenSession()) + { + using (ITransaction tr2 = BeginTransaction(session2)) + { + var p2 = session2.Get<Person>(person.Id); + p2.IdentificationNumber += 2; + + tr1.Commit(); + Assert.AreEqual(person.Version + 1, p1.Version); + + try + { + session2.SaveOrUpdate(p2); + session2.Flush(); + + tr2.Commit(); + Assert.Fail("StaleObjectStateException expected"); + } + catch (StaleObjectStateException sose) + { + Assert.AreEqual(typeof (Person).FullName, sose.EntityName); + Assert.AreEqual(p2.Id, sose.Identifier); + // as expected + } + } + } + } + } + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is MsSql2005Dialect || dialect is MsSql2008Dialect; + } + + private void SetAllowSnapshotIsolation(bool on) + { + using (ISession session = OpenSession()) + { + IDbCommand command = session.Connection.CreateCommand(); + command.CommandText = "ALTER DATABASE " + session.Connection.Database + " set allow_snapshot_isolation " + + (on ? "on" : "off"); + command.ExecuteNonQuery(); + } + } + + protected override void OnSetUp() + { + base.OnSetUp(); + + SetAllowSnapshotIsolation(true); + + person = new Person(); + person.IdentificationNumber = 123; + SavePerson(person); + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + { + using (ITransaction tr = session.BeginTransaction(IsolationLevel.Serializable)) + { + string hql = "from Person"; + session.Delete(hql); + session.Flush(); + tr.Commit(); + } + } + + SetAllowSnapshotIsolation(false); + + base.OnTearDown(); + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + configuration.SetProperty(Environment.SqlExceptionConverter, + typeof (SQLUpdateConflictToStaleStateExceptionConverter).AssemblyQualifiedName); + } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2009-05-24 05:24:03 UTC (rev 4372) +++ trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2009-05-24 06:13:38 UTC (rev 4373) @@ -356,6 +356,9 @@ <Compile Include="NHSpecificTest\NH1343\Product.cs" /> <Compile Include="NHSpecificTest\NH1343\ProductFixture.cs" /> <Compile Include="NHSpecificTest\NH1388\Fixture.cs" /> + <Compile Include="NHSpecificTest\NH1553\MsSQL\Person.cs" /> + <Compile Include="NHSpecificTest\NH1553\MsSQL\SnapshotIsolationUpdateConflictTest.cs" /> + <Compile Include="NHSpecificTest\NH1553\MsSQL\SQLUpdateConflictToStaleStateExceptionConverter.cs" /> <Compile Include="NHSpecificTest\NH1574\Principal.cs" /> <Compile Include="NHSpecificTest\NH1574\SpecializedPrincipal.cs" /> <Compile Include="NHSpecificTest\NH1574\SpecializedTeaml.cs" /> @@ -1831,6 +1834,7 @@ <EmbeddedResource Include="Ado\VerySimple.hbm.xml" /> <EmbeddedResource Include="Ado\AlmostSimple.hbm.xml" /> <Content Include="DynamicEntity\package.html" /> + <EmbeddedResource Include="NHSpecificTest\NH1553\MsSQL\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH1788\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH1794\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH1792\Mappings.hbm.xml" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |