|
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.
|