NHibernate Update Column = Column - 1 - nhibernate

I am unable to get the below update to execute in the context of nhibernate.
using(ITransaction transaction = session.BeginTransaction())
{
// FIRST I'm GETTING A LIST OF ITEMS IN A MANNER LIKE THIS
var itemsToDelete = session.QueryOver<Item>()
.Where(i => i.ReferenceObject.Id == otherIdValue)
.List<Item>();
// THEN I"M LOOPING THROUGH THEM
for(itemToDelete in itemsToDelete)
{
session.Delete(itemToDelete);
using (iDB2Command command = (iDB2Command)_session.Connection.CreateCommand())
{
command.CommandText = "update TABLE_NAME set sequence = (sequence - 1) where id = #someId and sequence > #sequenceNumberDeleted";
command.DeriveParameters();
command.Parameters["#someId"].Value = idValue;
command.Parameters["#sequenceNumberDeleted"].Value = itemToDelete.Sequence;
}
}
transaction.commit()
}
The problem seems to be with the sequence = (sequence - 1). Everytime the routine is called NHibernate is throwing an "unexpected row count" exception. While researching most articles I found related to this exception were caused by a trigger on the table updating other rows. In this case there aren't any triggers on the table. Additionally if I replace sequence = 5 or some other constant the update statement executes without any problems.
DATE TIME [10] ERROR App.Controllers.AController - Unexpected row count: 2; expected: 1
DATE TIME [10] ERROR App.Controllers.AController - at NHibernate.AdoNet.Expectations.BasicExpectation.VerifyOutcomeNonBatched(Int32 rowCount, IDbCommand statement)
at NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation)
at NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Int32 j, Object obj, SqlCommandInfo sql, ISessionImplementor session, Object[] loadedState)
at NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Object obj, ISessionImplementor session)
at NHibernate.Action.EntityDeleteAction.Execute()
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
at NHibernate.Engine.ActionQueue.ExecuteActions()
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()
at App.Controllers.AController.AMethod(Int32[] otherIdValues, Int32 someIdValue, String someReferenceValue) in <path>\App\Controllers\AController.cs:line 117
Can someone help point me in the right direction?
EDIT:
Diego is quite right. This is what I get for rushing.
The actual call to this code is inside a transaction and then I'm calling commit on the transaction.

I have since found the cause of the issue. The actual statements were not being generated sequentially as I thought they would be. Instead the update command was executing prior to the delete command and thus the delete command was throwing the unexpected row count exception, not the update command as I previously thought.

Related

TTC Error when requesting XMLType with Oracle.ManagedDataAccess.Client

I use a oracle12c database with ODAC from a .Net application
This 2 queries work in sqldevelopper :
SELECT *
FROM TABLE_A
WHERE (XMLCAST(XMLQUERY('/*/name' PASSING TABLE_A.XML_VALUE RETURNING CONTENT) AS NVARCHAR(225)) = 'admin')
SELECT *
FROM TABLE_A
WHERE (XMLCAST(XMLQUERY('count(/*)' PASSING TABLE_A.XML_VALUE RETURNING CONTENT) AS NUMBER) = 1)
But i have an TTC Error with these queries when i request via the Oracle.ManagedDataAccess.Client although this query work :
SELECT *
FROM TABLE_A
WHERE (XMLCAST(TABLE_A.XML_VALUE.extract('/*/name') AS NVARCHAR(225)) = 'admin')
Edit after Christian Shay comment
I don'use really SELECT *
I name all fields of TABLE_A and get the xmltype field with :
TABLE_A.XML_VALUE.getStringVal()
Second edit
The error message is not explicit, it's only : TTC Error
There is no InnerException
The StackTrace is :
à OracleInternal.TTC.TTCExecuteSql.ReceiveExecuteResponse(Accessor[]& defineAccessors, Accessor[] bindAccessors, Boolean bHasReturningParams, SQLMetaData& sqlMetaData, SqlStatementType statementType, Int64 noOfRowsFetchedLastTime, Int32 noOfRowsToFetch, Int32& noOfRowsFetched, Int64& queryId, Int32 longFetchSize, Int64 initialLOBFetchSize, Int64[] scnFromExecution, Boolean& bAllPureInputBinds, DataUnmarshaller& dataUnmarshaller, MarshalBindParameterValueHelper& marshalBindParamsHelper, Int64[]& rowsAffectedByArrayBind, Boolean bDefineDone, Boolean& bMoreThanOneRowAffectedByDmlWithRetClause, Boolean bLOBArrayFetchRequired)
à OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteReader(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, OracleDataReaderImpl& rdrImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[] scnForExecution, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, Int64& internalInitialLOBFS, OracleException& exceptionForArrayBindDML, Boolean isDescribeOnly, Boolean isFromEF)
à Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior)
à Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)
à System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
After investigation, the problem is due to the sql parameter.
This query failed in TTC Error:
SELECT TABLE_A.XML_VALUE.GetStringVal()
FROM TABLE_A
WHERE (XMLCAST(XMLQUERY('count(/*)' PASSING TABLE_A.XML_VALUE RETURNING CONTENT) AS NUMBER) = :Criterion0)
But if i replace the sql parameter :Criterion0 by the value 1, the query works
Unit test
Version of Oracle.DataAccess.dll : 4.121.1.0 ODAC RELEASE 3 BETA
Script sql :
CREATE TABLE XMLTEST
(
ID NUMBER (10) NOT NULL,
XML_VALUE XMLTYPE
);
INSERT INTO XMLTEST (ID,XML_VALUE) VALUES (1,XMLType('<root><data>TEST1</data><data>TEST2</data></root>'));
Test method :
[TestMethod]
public void ParameterOnXMLTypeTest()
{
string connectionString = "User ID=troopers;Password=troopers;Data Source=MYTST;";
Oracle.ManagedDataAccess.Client.OracleConnection connection = new Oracle.ManagedDataAccess.Client.OracleConnection(connectionString);
Oracle.ManagedDataAccess.Client.OracleParameter parameter = new Oracle.ManagedDataAccess.Client.OracleParameter("Param1", 2);
string query = "SELECT XMLTEST.ID FROM XMLTEST WHERE XMLCAST(XMLQUERY('count(/*/*)' PASSING XMLTEST.XML_VALUE RETURNING CONTENT) AS NUMBER) = :Param1";
Oracle.ManagedDataAccess.Client.OracleCommand command = new Oracle.ManagedDataAccess.Client.OracleCommand(query, connection);
command.Parameters.Add(parameter);
connection.Open();
using(IDataReader reader = command.ExecuteReader())
{
Assert.IsTrue(reader.Read());
Assert.AreEqual(1, reader.GetInt32(reader.GetOrdinal("ID")));
Assert.IsFalse(reader.Read());
}
connection.Close();
}
XMLType is not currently supported in ODP.NET Fully Managed driver as of this writing. This will change in a future version.
http://docs.oracle.com/html/E41125_02/intro004.htm

SqlCommand.ExecuteReader Fails to Report Errors

I'm executing a SQL command to create a new record in a database table and get the ID of the created record. However, there's a constraint error generated by the SQL command (uninitialized non-null field) which is not being picked up by the VB code. The code is roughly:-
connection = New SqlConnection(connection_string)
connection.Open()
sql_command = New SqlCommand(command) 'command = the SQL command to execute
sql_command.Connection = connection
sql_command.Parameters.AddRange(sql_parameters.ToArray()) ' sql_parameters is a parameter to the function
reader = sql_command.ExecuteReader()
If reader IsNot Nothing Then
If reader.HasRows Then
While reader.Read
response_handler(reader, data) 'response handler is a callback which populates the data object
End While
End If
reader.Close()
End If
The reader object is non-null but contains no data and no exception is generated. The SQL command is:-
insert into [table] ([column1], [column2], [column3], [column4])
output Inserted.[pk]
values (#1, #2, #3, #4)
Executing the SQL statement using SQL Server Management Studio, I get the error:-
Msg 515, Level 16, State 2, Line 2
Cannot insert the value NULL into column 'somecolumn', table 'tablename'; column does not allow nulls. INSERT fails.
I have also added a handler for the InfoMessage event on the SqlConnection object but that doesn't get called, even when I set FireInfoMessageEventOnUserErrors to true.
Why am I not getting an error and what is the correct way to ensure the error is reported to VB?
I'm using Visual Studio 2008.
The HasRows call returns false if there is an error. That way you will never see the error. Remove both If statements. The null check is redundant, the other one suppresses errors.
The severity level is 16 which does not interrupt the current session. If you want to throw a hard error you could add something like this directly after your insert...
If ##Error <> 0
Begin
Raiserror('Constraint Error Encountered',20,1) With Log;
End
The security context will have to have sysadmin rights in order to perform the RAISERROR WITH LOG, but if you can't do this I'm sure there are other ways to throw a hard error. In any event a warning isn't going to throw an error to your VB code.
I don't know exactly how it works internally, but the property HasRows is set by a private method within the SqlDataReader class called something like TryGetNextResult, which uses a lot of try/catch blocks to set a boolean output parameter, so this method never throws any exception, or even provides any feedback about any errors. What I can't work out is exactly how it realises there will be errors without even attempting the insert, but it does.
With this sample table:
CREATE TABLE T (ID INT IDENTITY, A INT NOT NULL);
I ran:
string sql = #"INSERT T (A) OUTPUT inserted.ID, inserted.A VALUES (1);";
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
using (var reader = command.ExecuteReader())
{
Console.WriteLine(reader.HasRows); // True
while (reader.Read())
{
Console.WriteLine(reader.GetInt32(1)); // 1
}
}
}
When I changed this to try and insert null, reader.HasRows was false, but by putting a breakpoint in I was able to test before reader.Read() was called, and this shows that the identity value of the table T was unchanged, so the reader can't be validating inserts by simply rolling back transactions. It is a mystery to me why a SqlDataReader is able to identify that this insert will fail before execution, but yet if the SQL is executed it will still attempt the insert before raising an error.
To further prove the behaviour I ran a similar test with a simple select command, and saw the same behaviour:
static void Main(string[] args)
{
string sql = #" SELECT Date = CAST(D AS DATE)
FROM (VALUES
(1, '20130129'),
(2, '20130130'),
(3, '20130131'),
(4, '20130132')
) t (A, D)
ORDER BY A;";
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
using (var reader = command.ExecuteReader())
{
Console.WriteLine(reader.HasRows); // false
while (reader.Read()) // Exception
{
Console.WriteLine(reader.GetString(0));
}
}
}
}
Again this shows, that somehow the SqlDataReader has picked up that there will be an error casting 20130132 to a date. If I remove this row from the SQL the HasRows property becomes true.
I realise this doesn't actually answer your question but it was too long for a comment, hopefully it will help a little, and/or maybe prompt somebody who knows a lot more about c# than me to add their own answer.

LINQ to SQL in Visual Studio, InvalidCastException with no call stack

I'm having a really hard time debugging some LINQ to SQL code because there is no call stack and I have no idea where the error is occurring. It doesn't seem to be a SQL error - it's in C#, but through searching around and everything else, I can't find any way to figure out which calculation is giving the error.
The source code is below and you can see the error that is thrown when I try to insert some new rows. The call stack is empty and the exception contains no information about what went wrong.
var groups =
from record in startTimes
group record by record.startTime
into g
select new
{
startTime = g.Key.GetValueOrDefault(),
totalTasks = g.Count(),
totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),
minDwell = g.Min(o => o.record.dwellTime).GetValueOrDefault(),
maxDwell = g.Max(o => o.record.dwellTime).GetValueOrDefault(),
avgDwell = g.Average(o => o.record.dwellTime).GetValueOrDefault(),
stdevDwell = g.Select(o => Convert.ToDouble(o.record.dwellTime)).StdDev(),
correct80 = g.Sum( o => o.record.correct80.GetValueOrDefault() ? 1 : 0),
wrong80 = g.Sum(o => o.record.wrong80.GetValueOrDefault() ? 1 : 0)
};
var statistics = groups.AsEnumerable().Select(
g => new e_activeSession()
{
workerId = wcopy,
startTime = g.startTime,
totalTasks = g.totalTasks,
totalTime = g.totalTime,
minDwell = g.minDwell,
maxDwell = g.maxDwell,
avgDwell = g.avgDwell,
stdevDwell = g.stdevDwell,
total80 = g.correct80 + g.wrong80,
correct80 = g.correct80,
percent80 = g.correct80 / (g.correct80 + g.wrong80)
}
);
// Put these rows into the table
_gzClasses.e_activeSessions.InsertAllOnSubmit(statistics);
_gzClasses.SubmitChanges();
Here's the stack trace for the exception. If anyone can decipher it, please tell me, because I have no clue how to read these anonymous types...
at System.Data.SqlClient.SqlBuffer.get_Double()
at System.Data.SqlClient.SqlDataReader.GetDouble(Int32 i)
at Read_<>f__AnonymousType2`9(ObjectMaterializer`1 )
at System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at System.Data.Linq.Table`1.InsertAllOnSubmit[TSubEntity](IEnumerable`1 entities)
Also, I've run this query in LINQPad and got the same error and stack trace. Finally, here's the database query generated by LINQ to SQL that was executed right before the error. I don't see any double conversions in it at all.
SELECT COALESCE([t5].[value22],'1/1/0001 12:00:00 AM') AS [startTime], [t5].[value] AS [totalTasks], COALESCE([t5].[value2],0) AS [totalTime], COALESCE([t5].[value3],0) AS [minDwell], COALESCE([t5].[value4],0) AS [maxDwell], COALESCE([t5].[value5],0) AS [avgDwell], [t5].[value6] AS [correct80], [t5].[value7] AS [wrong80], [t5].[value22]
FROM (
SELECT COUNT(*) AS [value], MAX([t4].[timeInSession]) AS [value2], MIN([t4].[dwellTime]) AS [value3], MAX([t4].[dwellTime]) AS [value4], AVG([t4].[dwellTime]) AS [value5], SUM([t4].[value3]) AS [value6], SUM([t4].[value]) AS [value7], [t4].[value2] AS [value22]
FROM (
SELECT
(CASE
WHEN (COALESCE([t3].[wrong80],0)) = 1 THEN #p4
ELSE #p5
END) AS [value], [t3].[workerID], [t3].[value2], [t3].[timeInSession], [t3].[dwellTime], [t3].[value] AS [value3]
FROM (
SELECT
(CASE
WHEN (COALESCE([t2].[correct80],0)) = 1 THEN #p2
ELSE #p3
END) AS [value], [t2].[wrong80], [t2].[workerID], [t2].[value] AS [value2], [t2].[timeInSession], [t2].[dwellTime]
FROM (
SELECT (
SELECT MAX([t1].[timeStamp])
FROM [dbo].[workerLog] AS [t1]
WHERE ([t1].[dwellTime] IS NULL) AND ([t1].[timeInSession] = #p0) AND ([t1].[workerID] = #p1) AND ([t1].[timeStamp] <= [t0].[timeStamp])
) AS [value], [t0].[workerID], [t0].[dwellTime], [t0].[timeInSession], [t0].[correct80], [t0].[wrong80]
FROM [dbo].[workerLog] AS [t0]
) AS [t2]
) AS [t3]
) AS [t4]
WHERE [t4].[workerID] = #p6
GROUP BY [t4].[value2]
) AS [t5]
Let's read that call stack:
at System.Data.SqlClient.SqlBuffer.get_Double()
at System.Data.SqlClient.SqlDataReader.GetDouble(Int32 i)
at Read_<>f__AnonymousType2`9(ObjectMaterializer`1 )
at System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at System.Data.Linq.Table`1.InsertAllOnSubmit[TSubEntity](IEnumerable`1 entities)
InsertAllOnSubmit is the top of the stack. It called ToList, which called List's "ctor" which means constructor.
That List constructor enumerates its parameter (a deferred query), causing the query to be resolved.
During the enumeration in the List constructor, we try to move to the next element in the query (WhereSelectEnumerableIterator.MoveNext) - this is an enumerator created by your call to Select after AsEnumerable. In order to move to the new element, what we actually need to do is drill deeper into the query (ObjectReader.MoveNext), which is going to create some object from the database results.
At this point, I'd like to point out that you didn't say the type for startTimes. I'm guessing startTimes is a LinqToSql query
So, that database query was turned into a DataReader (not captured on the stack), and we are extracting a row from the DataReader (Read_<>f__AnonymousType29(ObjectMaterializer1 )) so we can turn that row into the .net instance that is the result of the query.
In the process of extracting values from the row, there's a double value involved, so we try to extract that (.GetDouble(Int32 i) and .get_Double()), . Now, we know that a DataReader can think of its values as an object[] (reference SqlDataReader.GetValues). It seems reasonable that GetDouble might be implemented something like this:
//rubbish implementation, the point is - a cast from object to double is made
public double GetDouble(Int32 i) {
double result = (double) this.GetValues()[i];
return result;
}
So, the object in the data reader's array doesn't cast to double. The most likely posibility is that the object is null!
It is also remotely possible that some other type is present (string or byte[] or whatever), but if you used LinqToSql's designer to make a dbml file, and the tables in the database still agree with that dbml, this is unlikely.
I see that Convert.ToDouble is not in the call stack. This must have been done before - in the database. Convert.ToDouble is -never actually called-... instead it is translated to sql and run there. The rules for type conversion are different in the database.
If you try to convert null to a double in .net, you get an exception.
If you try to convert null to a double in sql, you get a null.
Ok, here is what I see, the 2nd line in the exception says
at System.Data.SqlClient.SqlDataReader.GetDouble(Int32 i)...
In your code you only convert to double once:
stdevDwell = g.Select(o => Convert.ToDouble(o.record.dwellTime)).StdDev(),
Try commenting out the g.Select(...) on that line and see if you still get the exception.

Get value from SPFieldUser with AllowMultipleValues fails only in a Timer Job

This one is weird.
I'm executing this code in a Timer Job in SharePoint 2010 ...
...
// Get the field by it's internal name
SPField field = item.Fields.GetFieldByInternalName(fieldInternalName);
if (field != null)
{
SPFieldUser userField = (SPFieldUser)field;
object value = null;
if (userField.AllowMultipleValues)
{
// Bug when getting field value in a timer job? Throws an ArgumentException
users = new SPFieldUserValueCollection(item.ParentList.ParentWeb, item[userField.Id].ToString());
}
else
{
// Get the value from the field, no exception
value = item[userField.Id];
}
}
...
This code works perfectly when run in a simple ConsoleApplication but when run in the context of a Timer Job in SharePoint 2010 it throws an ArgumentException in the line ...
users = new SPFieldUserValueCollection(item.ParentList.ParentWeb, item[userField.Id].ToString());
I've tried many variations to retreive a value from a SPFieldUser but all fail only when a Timer Job is executing it and the field has AllowMultipleValues property set to TRUE.
I have tried debugging with Reflector and it seems that the exception is being thrown here in SPListItem ...
public object this[Guid fieldId]
{
get
{
SPField fld = this.Fields[fieldId];
if (fld == null)
{
throw new ArgumentException();
}
return this.GetValue(fld, -1, false);
}
...
And this here would be the exception stack trace...
System.ArgumentException was caught
Message=Value does not fall within the expected range.
Source=Microsoft.SharePoint
StackTrace:
at Microsoft.SharePoint.SPFieldMap.GetColumnNumber(String strFieldName, Boolean bThrow)
at Microsoft.SharePoint.SPListItemCollection.GetColumnNumber(String groupName, Boolean bThrowException)
at Microsoft.SharePoint.SPListItemCollection.GetRawValue(String fieldname, Int32 iIndex, Boolean bThrow)
at Microsoft.SharePoint.SPListItem.GetValue(SPField fld, Int32 columnNumber, Boolean bRaw, Boolean bThrowException)
at Microsoft.SharePoint.SPListItem.get_Item(Guid fieldId)
at FOCAL.Point.Applications.Audits.AuditUtility.GetPeopleFromField(SPListItem item, String fieldInternalName)
Sighh... any thoughts?
This generally means that you have requested too many lookup fields in a single SPQuery which would cause too many self-joins of the true-lookup-table in the content database unless SharePoint Foundation throttled resources. There is a threshold setting that is at 8 lookups per query for ordinary users. Make sure your query only returns the necessary lookup or person/group fields. If you can't decrease the usage, then consider altering the threshold setting.

NHibernate 2nd lvl cache, custom query, sqldialect

I got trunk version of NH and FNH. When i try to add 2nd level cache, some parts of NHibernate forgets about chosen sqldialect.
Initial configuration:
var cfg = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connectionString)
.DefaultSchema("dbo")
.UseReflectionOptimizer()
.Mappings(m => ................);
Guilty custom query:
var sql = #"with Foo(col1,col2,col3)
as (select bla bla bla...)
Select bla bla bla from Foo";
list = Session.CreateSQLQuery(sql)
.AddEntity("fizz", typeof(Fizz))
.SomethingUnimportant();
When i change configuration to:
var cfg = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connectionString)
.DefaultSchema("dbo")
.UseReflectionOptimizer()
.Cache(c=>c
.UseQueryCache()
.ProviderClass<HashtableCacheProvider>())
.ShowSql())
.Mappings(m => ................);
Query throws error (WITH clause was added in mssql2008):
The query should start with 'SELECT' or 'SELECT DISTINCT'
[NotSupportedException: The query should start with 'SELECT' or 'SELECT DISTINCT']
NHibernate.Dialect.MsSql2000Dialect.GetAfterSelectInsertPoint(SqlString sql) +179
NHibernate.Dialect.MsSql2000Dialect.GetLimitString(SqlString querySqlString, Int32 offset, Int32 limit) +119
NHibernate.Dialect.MsSql2005Dialect.GetLimitString(SqlString querySqlString, Int32 offset, Int32 last) +127
NHibernate.Loader.Loader.PrepareQueryCommand(QueryParameters queryParameters, Boolean scroll, ISessionImplementor session) +725
NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +352
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +114
NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters) +205
Any ideas what exactly confuses nhibernate and how to fix it?
Guilty NHibernate code (in NHibernate/Dialect/MsSql200Dialect.cs):
private static int GetAfterSelectInsertPoint(SqlString sql)
{
if (sql.StartsWithCaseInsensitive("select distinct"))
{
return 15;
}
else if (sql.StartsWithCaseInsensitive("select"))
{
return 6;
}
throw new NotSupportedException
("The query should start with 'SELECT' or 'SELECT DISTINCT'");
}
}
Looks that .SetMaxResults(123) causes this. Fortunately, i can unbound that query.
Hopefully that will fix this.
I repaired the bug using Alkampfer's solution, but I created my own SQL dialect rather than patching the NHibernate source directly:
public class Sql2008DialectWithBugFixes : MsSql2008Dialect
{
public override SqlString GetLimitString(SqlString querySqlString, int offset, int last)
{
if (offset == 0)
{
return querySqlString.Insert(GetAfterSelectInsertPoint(querySqlString), " top " + last);
}
return base.GetLimitString(querySqlString, offset, last);
}
private static int GetAfterSelectInsertPoint(SqlString sql)
{
Int32 selectPosition;
if ((selectPosition = sql.IndexOfCaseInsensitive("select distinct")) >= 0)
{
return selectPosition + 15; // "select distinct".Length;
}
if ((selectPosition = sql.IndexOfCaseInsensitive("select")) >= 0)
{
return selectPosition + 6; // "select".Length;
}
throw new NotSupportedException("The query should start with 'SELECT' or 'SELECT DISTINCT'");
}
}
I had a similar issue (removing SetMaxResults also helped but I needed paging) and found out that the following NHibernate configuration property was causing this bug:
<property name="use_sql_comments">true</property>
It's certainly a bug, because the GetAfterSelectInsertPoint method doesn't take into account that SQL comments may be prepended to the SQL query.
Just set the use_sql_comments property to false and the problem disappears.
Just had the same problem using a similar query which has a WITH clause.
Unfortunately, my query populates a grid, with paging, so I have to keep SetMaxResults.
My solution was to rewrite using a Derived Table:
var sql = #"with Foo(col1,col2,col3)
as (select x1, x2, x3 from x join y blabla)
Select col1, col2, col3 from Foo
join B on B.col1 = Foo.col1";
becomes
var sql = #"Select col1, col2, col3 from
(select x1 as col1, x2 as col2, x3 as col3
from x join y blabla) as Foo
join B on B.col1 = Foo.col1";
Just to allow NHibernate to insert the " TOP x " string after the "select" string (6 characters from the begining)... No comment :(
T
It seems that there is some strange bug in the routine used to find the place in the query to insert the TOP clause (GetAfterSelectInsertPoint ) as told by Sandor. You can fix it directly in nh source (I actually patched 2.1 version I'm using in a project, you can find details here). So if you absolutely needs to enable comments with use_sql_comments you can :)
I encountered this problem when upgrading from 1.2 to 3.2 (I know, BIG jump eh?).
The issue in my case was that there is a leading space in front of the select statement in the hql, e.g. String hql = " select "...
With SQL2005 Dialect, this crashes with a "System.NotSupportedException: The query should start with 'SELECT'..." message.
The solution is to
create a unit test that fails, a good Test Driven Developer
should :)
remove the leading space from the " select..." statement
build and run the unit test
Just as i predicted - unbounding select is acceptable workaround.
Deleted SetMaxResults and it works.
We ran into this issue when upgrading to NHibernate version 3.3, but for a different reason...whitespace. We had a lot of sql strings that looked like this:
var sql = #"
select col1 from MyTable";
or:
var sql = #" select col1 from My Table";
These resulted in the "The query should start with 'SELECT' or 'SELECT DISTINCT'" errors because NHibernate doesn't trim the string before validating it.
We created a new dialect that trims the string first to get around this:
public class Sql2008DialectCustom : MsSql2008Dialect
{
public override SqlString GetLimitString(SqlString queryString, SqlString offset, SqlString limit)
{
var trimmedQueryString = queryString.Trim();
return base.GetLimitString(trimmedQueryString, offset, limit);
}
}