NHibernate 2nd lvl cache, custom query, sqldialect - nhibernate

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);
}
}

Related

EF could not translate expression to sql

It looks like EF is not able to translate the express in the following code, this is the call
Counter lastCounter = unitOfWork.CounterRepository.FindLast(x => x.Div == counter.Div, x => x.Div);
this is the method
public Counter FindLast(Expression<Func<Counter, bool>> predicate, params Expression<Func<Counter, object>>[] includedProperties)
{
IQueryable<Counter> set = context.Set<Counter>().Where(predicate);
foreach (var includeProperty in includedProperties)
{
set = set.Include(includeProperty);
}
return set.Last();
}
Any idea what could be the problem?
It's quite simple, really: Entity Framework just does not support Last(). The reason for this is that in SQL, you also cannot select the last element (i.e. you have SELECT TOP but don't have SELECT BOTTOM).
See https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/supported-and-unsupported-linq-methods-linq-to-entities

Write Round in NHibernate criteria

I need to write this in NHibernate Criteria as a projection:
The subAlias is _not_ the root alias, so {alias} cannot replace the correct sql alias, and my problem is that other parts of the query makes the subAlias vary in the generated sql
ROUND(alias.Property / parameterValueFromMethodParameter + ", 0)
* parameterValueFromMethodParameter2 AS SQLAlias
This is how far (off) I got:
.Add(Projections.SqlFunction(new VarArgsSQLFunction("(", "/", ")")
, NHibernateUtil.Int32
, Projections.SqlFunction("round"
, NHibernateUtil.Decimal
, Projections.Property("subAlias.Property"))), "SQLAlias"))
This produces the following SQL code:
ROUND( subAlias3(4).Property
)AS y1_
Does anyone have experience with projections like this?
I found this patch i hibernate, but seems like it was not implemented.
If I understand your example properly, the most easy solution would be to use SQL projection:
// the parameterValueFromMethodParameter
// and parameterValueFromMethodParameter2
var computationParams = new object[] {2, 4}; // just an example numbers
// SQL To be generated
// see that here we work with COLUMN name, not property
var sqlSnippet = " ( ROUND({{alias}}.ColumnName / {0}, 0) * {1} ) AS computed ";
// put that all together
var projectSql = string.Format(sqlSnippet, computationParams);
// IProjection
var projection = Projections.SqlProjection(projectSql, new string[0], new IType[0]);
// add it to SELECT clause
criteria.SetProjection(Projections.ProjectionList()
.Add(projection)
...
);
That should work...
I Solved it by writing my own SQL IProjection. With a litte help from this example.
public SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
{
string replacedString = Regex.Replace(
this.sql,
#"{([a-zA-Z_]((\.)?[a-zA-Z0-9_])*)}",
m =>
{
ICriteria critter = criteria.GetCriteriaByAlias(m.Groups[1].Value);
if (critter != null)
{
return criteriaQuery.GetSQLAlias(critter);
}
return m.Groups[0].Value;
});
return new SqlString(replacedString);
}
So now I can do (In a SqlGroupProjection):
ROUND({subAlias}.XCoord / " + aggregationSize + ", 0) * " + aggregationSize + " AS SQLAlias
There are some other attempts on extending (N)Hibernate to handle this alias in raw SQL problem:
Expression.Sql should support aliases other than {alias}
Support for referencing non-root entities in Criteria SQL expressions

How to call Oracle's regexp_like function with Nhibernate QueryOver?

I'm aware I need to use Restrictions.Eq and Projections.SqlFunction, but I've been trying for hours without any success (my test app just crashes). Does anyone have an QueryOver example that would do the following in Oracle:
SELECT
*
FROM
V_LOG_ENTRIES
WHERE
regexp_like(ENTRY_TEXT, '(\WPlaced\W)');
UPDATE: Okay, I think part of the problem is that Restrictions.Eq expects an equality, but there is no equality in this case, it's just a function call in the WHERE clause...
The syntax should be like this:
// this is inlined string, but could be concatenated from some params
var sql = #" regexp_like(ENTRY_TEXT, '(\WPlaced\W)') " +
" AS isLike";
var sqlString = new SqlString(sql);
// the ICriterion
var criterion = new NHibernate.Criterion.SQLCriterion(sqlString
, new string[] {}
, new IType[] {}
);
// the query
var query = session.QueryOver<LogEntry>()
.Where(criterion)
...

Is there any advantage to PreparedStatement#setInt vs inline SQL?

I've read Give me Parameterized SQL or give me death numerous times.
The advantage of Parameterized SQL for Strings, Dates, and floating-point numbers is very obvious and indisputable.
My question is: what about ints?
I ask because, oftentimes, if I'm writing a query or update and the only parameter is an int, I'll just write an inline sql and append the int as a shortcut ("select * from table where id = " + id).
My question: Are there any advantages to using Parameterized SQL for ints alone?
To illustrate with Java:
Are there any advantage to this:
Connection conn;
int id;
String sql = "select * from table where id = ?";
try (PreparedStatement p_stmt = conn.prepareStatement(sql)) {
p_stmt.setInt(1, id);
ResultSet results = p_stmt.executeQuery();
// ...
} catch (SQLException e) {
// ...
}
over this:
Connection conn;
int id;
String sql = "select * from table where id = " + id;
try (Statement stmt = conn.createStatement()) {
ResultSet results = stmt.executeQuery(sql);
// ...
} catch (SQLException e) {
// ...
}
I would say the biggest advantage would be consistency. If you decide that all SQL built by string concatenation is "wrong", it's easier to verify that your code is "right", compared to a rule like "All SQL built by string concatenation is wrong, except that which deals with ints as parameters".
Another case, say: down the line, you want to introduce sorting or grouping to the query, suddenly, your line turns into something like this:
String sql = "select * from table where id = " + id + " order by somecolumn";
And hopefully you remembered the space before order. And that everyone after you does also.
There is much to be said for doing things only one way, especially when that one way is the right thing most of the time.

ResultSet coming as empty after executing query

I have a query
SELECT instance_guid FROM service_instances WHERE service_template_guid='E578F99360A86E4EE043C28DE50A1D84' AND service_family_name='TEST'
Directly executing this returns me
4FEFDE7671A760A8DC8FC63CFBFC8316
F2F9DF641D8E2CACC03175A7A628D51D
Now I am trying same code from JDBC.
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = executionContext.getConnection();
if (conn != null) {
ps = (PreparedStatement)conn.prepareStatement(query);
if (params == null) params = new Object[0];
for (int i=0;i<params.length;i++) {
if (params[i] instanceof Integer) {
ps.setInt(i+1, ((Integer)params[i]).intValue());
} else if (params[i] instanceof java.util.Date) {
((PreparedStatement)ps).setDATE(i+1, new oracle.sql.DATE((new java.sql.Timestamp(((Date)params[i]).getTime()))));
//ps.setObject(i+1, new oracle.sql.DATE(new Time(((Date)params[i]).getTime())));
} else {
if (params[i] == null) params[i] = "";
ps.setString(i+1, params[i].toString());
}
}
rs = ps.executeQuery();
I see params[0] =E578F99360A86E4EE043C28DE50A1D84 and params[1]=TEST
But the resultSet is empty and not getting the result.I debugged but not much help?
Can you please let me know Am i trying right?
In java its defined as below
final static private String INSTANCE_GUID_BY_TEMPLATE_GUID =
"SELECT instance_guid FROM service_instances WHERE service_template_guid=? AND service_family_name=? "
SERVICE_FAMILY_NAME NOT NULL VARCHAR2(256)
SERVICE_TEMPLATE_GUID NOT NULL RAW(16 BYTE)
First and foremost this breaks every sql mapping pattern I have ever seen.
String sql = "SELECT instance_guid FROM service_instances WHERE service_template_guid=? AND service_family_name=?";
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = executionContext.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1,guid);
ps.setString(2,family);
rs = ps.executeQuery();
while(rs.next(){...}
...
}
You should not be dynamically figuring out the data types as they come in, unless you are trying to write some code to port from database X to database Y.
UPDATE
I see you are using RAW as a datatype, from this post:
As described in the Oracle JDBC Developer's guide and reference 11g,
when using a RAW column, you can treat it as a BINARY or VARBINARY
JDBC type, which means you can use the JDBC standard methods
getBytes() and setBytes() which returns or accepts a byte[]. The other
options is to use the Oracle driver specific extensions getRAW() and
setRAW() which return or accept a oracle.sql.RAW. Using these two will
require you to unwrap and/or cast to the specific Oracle
implementation class.
Further from a code readability standpoint, your solution makes it painful for a new developer to take over. Far too often I see people making sql be "dynamic" when in reality 99% of the time you don't need this level of dynamic query building. It sounds good in most people's heads but it just causes pain and suffering in the SDLC.