Is it possible to add where clauses dynamically to an NHibernate Query?
I have a collection of Clauses I need to Loop through and add a Where clause if required - i.e. if the user has entered multiple search criteria.
I Can write single queries, no problem, like this: get All Names beginning with 'a':
IEnumerable<Customer> customers = nHibernateSession.Query<Customer>().Where(x => x.Name.StartsWith("a")).ToList();
but I do not know how to then Add another where clause to this
Effectively I need something that is like this:
foreach (clauses in SelectionClauses)
{
//add a .Where(Clause) to The Session.Query
}
I suspect That The nHibernateSession.Query cannot be used this way...
Does anyone know How to do this?
You can do this with the Linq provider (session.Query) by doing something like:
var query = session.Query<MyClass>();
foreach(var clause in clauses)
query = query.Where(clause.BuildExpression());
Where BuildExpression returns Expression> for example
However this can get painful if you are using string name for your properties, and it will be a lot easier to achieve your result using session.QueryOver or session.CreateCriteria both of which support querying properties by their name instead of via expressions.
Related
I have quite a complex query that applies different layers of filtering and requires ordering/paging.
In pseudo-SQL I want to do the following:
SELECT ... FROM a WHERE a.id in (SELECT a.id FROM a WHERE [...] limit 10,10)
I use Criteria and DetachedCriteria, something like this:
Criteria criteria = session.createCriteria(xx.class, "xx");
DetachedCriteria idFilterSubQuery = DetachedCriteria.forClass(xx.class, "xx");
//ADD ALL FILTERS TO idFilterSubQuery
//ADD ALL OUTER_JOINS TO criteria and idFilterSubQuery
criteria.add(Subqueries.propertyIn("id", idFilterSubQuery));
Now for the paging, I can't add it to criteria, since the outer-joins mess it up... So I need to add it to idFilterSubQuery... BUT, DetachedCriteria does not have setFirstResult() and setMaxResult()... But you can't add normal Criteria as SubQuery (I haven't found a way at least)...
I would like a solution that doesn't fetch all id's first, and the full objects afterwards (reduce a round-trip to the DB)
Any ideas how to achieve this? So either use Criteria as SubQuery or add paging to a DetachedCriteria...
I'm using Hibernate 4.2.21
I am exposing a web service that constructs a SQL SELECT statement and accepts parameters from a controller. The generic functions looks like this:
DataTable SqlSelect (string select, string from, string Where, string orderby = "", string groupBy = "")
{
string sql = "SELECT " + select + " FROM " + from + " WHERE " + where
(orderby =="") ? "" : "ORDER BY " + orderby ...
//do other stuff
}
Now what makes me worry is the fact that base on given function above, the user may now inject harmful commands like:
SqlSelect("DROP TABLE 'TABLENAME'", "INFORMATION_SCHEMA.TABLES", "TABLE_NAME like %%'");
Which I want to prevent.
Now my question is: what is the best thing I can do to prevent user to UPDATE, MODIFY, DELETE, TRUNCATE tables and only allow SELECT statement (can use something like READ oNLY?)
Note: this is similar to this question but the user was working on PHP, while I'm in ASP.NET MVC, also what I want to achieve here is only allow SELECT or 'GET' statement.
Do not do it this way. You need to parameterize your queries which means that you cannot accept SQL text as an input. I have found many attempts by developers to detect SQL injection attacks and I almost always find a way of getting past their logic.
If you need to be able to dynamically construct any SELECT query based on any table in your database then you could easily create a class that indicates the table, select columns and where predicate columns as enums, and the values of the where predicates. Concatenate the SQL text based on this class and include the predicate values using SqlParameters.
It is just one example, but for sure you do not want to accept SQL text.
Make use of a data reader https://msdn.microsoft.com/en-us/library/haa3afyz(v=vs.110).aspx. You can catch any exceptions when you call ExecuteReader().
However I would advise against exposing this kind of generic functionality to client side code. You should rather only provide controlled access to your data via an appropriate data layer using something like the repository pattern.
Looking at the documentation, there are fairly good instructions on how to build up a SQL query.
My code looks like this:
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('PropertyPage')->selectField('PropertyType')->setDistinct(true);
I'm aiming to get the following SQL:
SELECT DISTINCT PropertyType FROM PropertyPage
but instead I'm getting back this:
SELECT DISTINCT *, PropertyType FROM PropertyPage
Even their own example seems to give back a 'SELECT DISTINCT *. How can I avoid this?
Why do you use SQLQuery directly?
With pure ORM it should go like this:
$result = PropertyPage::get()->setQueriedColumns(array('PropertyType'))->distinct();
which returns a DataList you can loop over.
See setQueriedColumns() and distinct()
What version of SS framework are you using? Distinct was added in 3.1.7 iirc.
Just to add to #wmk's answer as well as directly address how to do it with SQLQuery, your call to selectField is the reason why that query happened. The documentation to selectField shows that it adds an additional field to select, not restrict the list to just that field.
The reason why their examples (A and B) also had the issue is that the first parameter for the constructor for SQLQuery is a default value of "*" for select statement.
To still use your code as a base, replace your use of selectField with setSelect. Your query will then look like this:
SELECT DISTINCT PropertyType FROM PropertyPage
It isn't bad to directly query the database using SQLQuery especially if you really just want the raw data of a particular column or the result itself cannot be expressed against a DataObject (eg. using column aliases). You would also have a small performance improvement that PHP would not have to instantiate a DataObject.
While saying that, it can be far more useful having a DataObject and the various functions that it exposes per record.
Using nHibernate QueryOver, if I want to enforce a projection for performance, are "Select" and "Where" the same thing? In other words, will ..
var member = session.QueryOver<Member>()
.Select( projections => projections.Email == model.Email )
.Take(1).SingleOrDefault();
Run the same as
var member = session.QueryOver<Member>()
.Where( context => context.Email == model.Email )
.Take(1).SingleOrDefault();
Or is there a difference in the two?
Select projects (you could also say maps); Where filters. This is the same as SQL and all LINQ providers (and QueryOver is also sort of a LINQ provider). It seems that in this case you want to filter, not project, so you need Where
No offense intended, but I think the best way to answer a question like the one you asked is to try it. Sometimes things become clearer when you can see the output.
That said, when you use Select, you're telling NHibernate how to project your data. This determines the final makeup of the data resulting from the query. There's a little bit more to this, but that's the general idea. You use Where when you want to specify the criteria that the data that you are querying should satisfy.
In my application the user can defines search-conditions. He can choose a column, set an operator (equals, like, greater than, less or equal than, etc.) and give in the value. After the user clicks on a button and the application should do a search on the database with the condition. I use NHibernate and ask me now, what is the efficientest way to do this with NHibernate.
Should I create a query with it like (Column=Name, Operator=Like, Value=%John%)
var a = session.CreateCriteria<Customer>();
a.Add(Restrictions.Like("Name", "%John%"));
return a.List<Customer>();
Or should I do this with HQL:
var q = session.CreateQuery("from Customer where " + where);
return q.List<Customer >();
Or is there a more bether solution?
Thanks for your help.
Best Regards, Thomas
You can use either one of them. There might be hardly an differences between the two. But whatever you do make sure that your column names are in constants or are mapped to the column name otherwise your repository will be tightly coupled to your model definition i.e if you update the columnname you will have to go and update these statements yourself.
And when you are building the where clause makes ure you have a function that appends the right query. you will probably be having a switch case statement for the same.
In term of efficence there is no difference. In the HQL version I would prefer use a parameter instead of adding the where part as a string.
If you are using NH3.0 you can consider using QueryOver too, to avoid using string to describe your properties