I'm a bit of a NHibernate newbie and Im taking on some code written by another developer. I want to find out how NHibernate converts lambda based criteria into SQL.
I know in Linq to SQL using Lambda expressions on queries means that the whole thing is turned into an expression tree and then into SQL (where possible) by the Linq to SQL provider. This can be seen by doing DataContext.Log = Console.Out.
But what about an NHibernate criteria expression where Linq to NHibernate isnt being used?
The following namespaces are imported...
using NHibernate;
using NHibernate.Criterion;
using NHibernate.LambdaExtensions;
.. and the criteria code looks like this...
return Session.CreateCriteria<MyObjectType>()
.Add<MyObjectType>(x => x.Id == id)
.UniqueResult<MyObjectType>();
Will this be turned into an SQL statement e.g.
Select distinct * from table where id = [param]
... or will the whole dataset be pulled into memory giving a List and then have the lambda expressions applied against the objects. e.g.
return List<MyObject>.Where(x => x.id = id) [or something similar].
I', not sure if my importing NHibernate.LambdaExtensions provides a sort of translation into SQL.
It is turned to an HQL statement first (enable logging and look at the console for the statements) and then to an SQL and sent to the database.
It does not select the whole table to memory and filters there.
Related
During a pen-test of an in-development app, I had a scary result where there was a successful SQL injection due to NH executing a query without parameters and instead injecting a string right into the query.
We're using NHibernate 4 and Fluent NHibernate 1.4. Dialect is SQL 2008 running against SQL Azure.
The successful query looked like this (pulled from SQL Azure audit logs)
select columns from [Table] table0_ where table0_.TenantId='1555%00';IF(5672=5672) SELECT 5672 ELSE DROP FUNCTION GziQ--' and table0_.Active=1
As you can see, the injectable string was able to terminate the string and inject a DROP FUNCTION then comment out the rest of the statement.
Running this locally and using NHibernate Profiler, we see the value being passed as a parameter as-expected:
select columns from [Table] table0_ where table0_.TenantId = #p0 and table0_.Active=1
On the code side, the data is queried using an IQueryable interface (code inlined and simplified for example purposes):
Session.Query<TEntityType>().Cachable().Table.SingleOrDefault(k => k.TenantId == tenantId && k.Active);
Under what circumstanes would NHibernate not parameterize this query and instead inject the string directly into a plain SQL statement?
I know that we can easily apply filters to query with additional where conditions with NHibernate, but is it possible to apply a filter when doing an update or delete?
If it is, how can I achieve that?
Yes it is possible, using HQL (Hibernate Query Language)
Here's an example of a batch update
IQuery updateQuery = this.Session.CreateQuery("update TransferItem set Status = :newStatus where Status = :oldStatus")
.SetParameter("oldStatus", DownloadStatus.Active)
.SetParameter("newStatus", DownloadStatus.Queued);
updateQuery.ExecuteUpdate();
NHibernate applies the configured mappings to create and run the following SQL:
update cms_TransferItem set Status=#p0 where Status=#p1
Here's an example of a batch delete
IQuery deleteQuery = this.Session.CreateQuery("delete TransferItem ti WHERE ti.Status = :statusToGo")
.SetParameter("statusToGo", DownloadStatus.Completed);
deleteQuery.ExecuteUpdate();
Which executes SQL like this:
delete from cms_TransferItem where Status=#p0
You might ask, if you have to work with a query language, why not just write raw SQL? When you use HQL you are working with the conceptual business objects that the rest of the .NET code is working with. The benefits of an ORM tool is that, for much of the code, the database tables and object-to-table mappings are abstracted away. With HQL you are continuing to interact with the object layer, rather than directly with the database tables.
.Where(x => !x.Rated)
This creates sql that looks like:
not (cdrcalltmp0_.Rated=1)
Our dba says I have to remove the not for some filtered index to work.
.Where(x => x.Rated == false)
This creates sql that looks like:
cdrcalltmp0_.Rated=#p2 order by cdrcalltmp0_.Created asc'
This doesn't work because of the parameter.
He would like this sql:
cdrcalltmp0_.Rated=0 order by cdrcalltmp0_.Created asc'
Is it possible to make nhibernate not use parameters?
So that a filtered index works.
Preface:
The following answer is assuming you are using SQL Server 2008. If you are not, then it is quite possible that the database technology in question does not support indexes when using the NOT operator. So, if your using SQL Server 2008...
Your DBA doesn't know what he's talking about.
The following syntax
NOT ( SomeTableAlias.SomeTableColumn = 1 )
will absolutely be understood by the SQL Server Query Analyzer. I've got queries from NHibernate that look exactly like the above syntax and they are indeed using the proper indexes.
And to answer your question, no. NHibernate always uses parameters when it creates the SQL for you. Parameterized queries are extremely common place, even when using traditional ADO.NET yourself.
The only way to get NHibernate to not use parameters is if you supply the SQL it needs to execute yourself using the session.CreateSQLQuery() method.
At any rate, the above line you posted:
Where(x => x.Rated == false) This creates sql that looks like: cdrcalltmp0_.Rated=#p2 order by cdrcalltmp0_.Created asc'
is completely valid. When SQL Server receives the parameterized query, it will use whatever index is on your Rated column.
If your DBA still doubts you, tell him to run the query in Sql Server Management Studio with the "Display Estimated Execution Plan" feature on. That will prove that the query is using the index.
I tried to call linq query from sliverlight application to web service which is 'ADD.NET Entity Data Model' with 'WCF Data Service'. The linq below is working (e.g.using pre-defined table & field names):
var query = from o in context.ORDER
where o.NUMBER == 1
select o;
((DataServiceQuery<ORDER>)query).BeginExecute(OnQueryComplete, query);
But I need dynamically assign different table and fields names to the linq query. Is there any way? Do I need to write a method in WCF to execute any sql command?
Thanks for any help.
You can use the Dynamic Linq samples to provide dynamic field names in where clauses - see: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
Further, you can do this in a type-safe way using PredicateBuilder - http://www.albahari.com/nutshell/predicatebuilder.aspx
For more dynamic behavior - including dynamic table names - the only Linq option I can think of is to compile some code at runtime within your app using the CSharpCodeProvider (http://support.microsoft.com/kb/304655). However, obviously you need to be careful with security when offering this from a web service.
Currently I use a block of code like this, to fetch a set of DB objects with matching IDs.
List<subjects> getSubjectsById(List<long> subjectIDs){
return ctx.tagSubjects.Where(t => subjectIDs.Contains(t.id)).ToList();
}
But this is really inefficient, because it requires the entire table to be read from the database and then filtered inside of C#.
What I would rather do would be something the equivelent of:
SELECT * FROM subjects WHERE subjects.id IN (1,2,3,4,5,...);
The big difference is that in the first example the filtering is happening inside the C# code, and in the second the filtering is done on the SQL server (where the data is).
Is there a [better] way to do this with LINQ?
Where did you find out that it downloads the entire table from SQL Server?
I'm sure it does what you want. It translates the query to a parameterized IN clause like:
... IN (#p1, #p2, #p3)
and passes the contents of the list as values to those parameters. You can confirm this with tools such as SQL Profiler and LINQ to SQL debugger visualizer or set the DataContext.Log property to console (before executing the query) and read the generated SQL:
dataContext.Log = Console.Out;