Advanced search with distances using NHibernate and SQL Server Geography - nhibernate

I've got an existing advanced search method in a repository that checks a FormCollection for the existence of search criteria, and if present, adds a criterion to the search e.g.
public IList<Residence> GetForAdvancedSearch(FormCollection collection)
{
var criteria = Session.CreateCriteria(typeof(Residence))
.SetResultTransformer(new DistinctRootEntityResultTransformer());
if (collection["MinBedrooms"] != null)
{
criteria
.Add(Restrictions.Ge("Bedrooms", int.Parse(collection["MinBedrooms"])));
}
// ... many criteria omitted for brevity
return criteria.List<Residence>();
}
I've also got a basic distance search to find how far each residence is from the search criteria. The HBM for the query is
<sql-query name="Residence.Nearest">
<return alias="residence" class="Residences.Domain.Residence, Residences"/>
<return-scalar column="Distance" type="float"/>
SELECT R.*, dbo.GetDistance(:point, R.Coordinate) AS Distance
FROM Residence R
WHERE Distance < 10
ORDER BY Distance
</sql-query>
I had to define a function to calculate the distance, as there was no way to get NHibernate to escape the colons in the geography function:
CREATE FUNCTION dbo.GetDistance
(
#firstPoint nvarchar(100),
#secondPoint GEOMETRY
)
RETURNS float
AS
BEGIN
RETURN GEOGRAPHY::STGeomFromText(
#firstPoint, 4326).STDistance(#secondPoint.STAsText()) / 1609.344
END
And the repository calls the named query thus:
return Session
.GetNamedQuery("Residence.Nearest")
.SetString("point", String.Format("POINT({0} {1})", latitude, longitude))
.List();
So my question is; how do I combine the two (or start from scratch), so I can filter the advanced search results to include only residences within 10 miles of the search location?
UPDATE I have tried using NHibernate.Spatial with the following code:
criteria.Add(SpatialExpression.IsWithinDistance(
"Coordinate", new Coordinate(latitude, longitude), 10));
but SpatialExpression.IsWithinDistance returned a System.NotImplementedException.

Have you seen the NHibernate.Spatial project? This may provide an easy solution to your problem.
The alternative is to create your own implementation of ICriterion - this is not too tricky if you derive from AbstractCriterion and you target your particular database platform. This would then allow you to combine your distance function with other criteria.

Create a projection that, in effect, adds a new distance column to the results, which is calculated by called the UDF, and then add a restriction to it:
var query = String.Format(
"dbo.GetDistance('POINT({0} {1}', Coordinate) AS Distance",
latitude, longitude);
criteria
.Add(Restrictions.Le(Projections.SqlProjection(
query,
new [] {"Distance"},
new [] {NHibernateUtil.Double}), 10));
UPDATE
n.b. Although this must have worked when I posted it, it doesn't work any more. NHibernate doesn't like the '.' after dbo, and says
"could not resolve property: dbo of: Residences.Domain.Residence".
If I remove the 'dbo.' I get
"'GetDistance' is not a recognized built-in function name."

Related

Slick plain sql query with pagination

I have something like this, using Akka, Alpakka + Slick
Slick
.source(
sql"""select #${onlyTheseColumns.mkString(",")} from #${dbSource.table}"""
.as[Map[String, String]]
.withStatementParameters(rsType = ResultSetType.ForwardOnly, rsConcurrency = ResultSetConcurrency.ReadOnly, fetchSize = batchSize)
.transactionally
).map( doSomething )...
I want to update this plain sql query with skipping the first N-th element.
But that is very DB specific.
Is is possible to get the pagination bit generated by Slick? [like for type-safe queries one just do a drop, filter, take?]
ps: I don't have the Schema, so I cannot go the type-safe way, just want all tables as Map, filter, drop etc on them.
ps2: at akka level, the flow.drop works, but it's not optimal/slow, coz it still consumes the rows.
Cheers
Since you are using the plain SQL, you have to provide a workable SQL in code snippet. Plain SQL may not type-safe, but agile.
BTW, the most optimal way is to skip N-th element by Database, such as limit in mysql.
depending on your database engine, you could use something like
val page = 1
val pageSize = 10
val query = sql"""
select #${onlyTheseColumns.mkString(",")}
from #${dbSource.table}
limit #${pageSize + 1}
offset #${pageSize * (page - 1)}
"""
the pageSize+1 part tells you whether the next page exists
I want to update this plain sql query with skipping the first N-th element. But that is very DB specific.
As you're concerned about changing the SQL for different databases, I suggest you abstract away that part of the SQL and decide what to do based on the Slick profile being used.
If you are working with multiple database product, you've probably already abstracted away from any specific profile, perhaps using JdbcProfile. In that case you could place your "skip N elements" helper in a class and use the active slickProfile to decide on the SQL to use. (As an alternative you could of course check via some other means, such as an environment value you set).
In practice that could be something like this:
case class Paginate(profile: slick.jdbc.JdbcProfile) {
// Return the correct LIMIT/OFFSET SQL for the current Slick profile
def page(size: Int, firstRow: Int): String =
if (profile.isInstanceOf[slick.jdbc.H2Profile]) {
s"LIMIT $size OFFSET $firstRow"
} else if (profile.isInstanceOf[slick.jdbc.MySQLProfile]) {
s"LIMIT $firstRow, $size"
} else {
// And so on... or a default
// Danger: I've no idea if the above SQL is correct - it's just placeholder
???
}
}
Which you could use as:
// Import your profile
import slick.jdbc.H2Profile.api._
val paginate = Paginate(slickProfile)
val action: DBIO[Seq[Int]] =
sql""" SELECT cols FROM table #${paginate.page(100, 10)}""".as[Int]
In this way, you get to isolate (and control) RDBMS-specific SQL in one place.
To make the helper more usable, and as slickProfile is implicit, you could instead write:
def page(size: Int, firstRow: Int)(implicit profile: slick.jdbc.JdbcProfile) =
// Logic for deciding on SQL goes here
I feel obliged to comment that using a splice (#$) in plain SQL opens you to SQL injection attacks if any of the values are provided by a user.

How to use LINQ to Entities to make a left join using a static value

I've got a few tables, Deployment, Deployment_Report and Workflow. In the event that the deployment is being reviewed they join together so you can see all details in the report. If a revision is going out, the new workflow doesn't exist yet new workflow is going into place so I'd like the values to return null as the revision doesn't exist yet.
Complications aside, this is a sample of the SQL that I'd like to have run:
DECLARE #WorkflowID int
SET #WorkflowID = 399 -- Set to -1 if new
SELECT *
FROM Deployment d
LEFT JOIN Deployment_Report r
ON d.FSJ_Deployment_ID = r.FSJ_Deployment_ID
AND r.Workflow_ID = #WorkflowID
WHERE d.FSJ_Deployment_ID = 339
The above in SQL works great and returns the full record if viewing an active workflow, or the left side of the record with empty fields for revision details which haven't been supplied in the event that a new report is being generated.
Using various samples around S.O. I've produced some Entity to SQL based on a few multiple on statements but I feel like I'm missing something fundamental to make this work:
int Workflow_ID = 399 // or -1 if new, just like the above example
from d in context.Deployments
join r in context.Deployment_Reports.DefaultIfEmpty()
on
new { d.Deployment_ID, Workflow_ID }
equals
new { r.Deployment_ID, r.Workflow_ID }
where d.FSJ_Deployment_ID == fsj_deployment_id
select new
{
...
}
Is the SQL query above possible to create using LINQ to Entities without employing Entity SQL? This is the first time I've needed to create such a join since it's very confusing to look at but in the report it's the only way to do it right since it should only return one record at all times.
The workflow ID is a value passed in to the call to retrieve the data source so in the outgoing query it would be considered a static value (for lack of better terminology on my part)
First of all don't kill yourself on learning the intricacies of EF as there are a LOT of things to learn about it. Unfortunately our deadlines don't like the learning curve!
Here's examples to learn over time:
http://msdn.microsoft.com/en-us/library/bb397895.aspx
In the mean time I've found this very nice workaround using EF for this kind of thing:
var query = "SELECT * Deployment d JOIN Deployment_Report r d.FSJ_Deployment_ID = r.Workflow_ID = #WorkflowID d.FSJ_Deployment_ID = 339"
var parm = new SqlParameter(parameterName="WorkFlowID" value = myvalue);
using (var db = new MyEntities()){
db.Database.SqlQuery<MyReturnType>(query, parm.ToArray());
}
All you have to do is create a model for what you want SQL to return and it will fill in all the values you want. The values you are after are all the fields that are returned by the "Select *"...
There's even a really cool way to get EF to help you. First find the table with the most fields, and get EF to generated the model for you. Then you can write another class that inherits from that class adding in the other fields you want. SQL is able to find all fields added regardless of class hierarchy. It makes your job simple.
Warning, make sure your filed names in the class are exactly the same (case sensitive) as those in the database. The goal is to make a super class model that contains all the fields of all the join activity. SQL just knows how to put them into that resultant class giving you strong typing ability and even more important use-ability with LINQ
You can even use dataannotations in the Super Class Model for displaying other names you prefer to the User, this is a super nice way to keep the table field names but show the user something more user friendly.

How to change sql generated by linq-to-entities?

I am querying a MS SQL database using Linq and Entity Framework Code First. The requirement is to be able to run a WHERE SomeColumn LIKE '%sometext'clause against the table.
This, on the surface, is a simple requirement that could be accomplished using a simple Linq query like this:
var results = new List<MyTable>();
using(var context = new MyContext())
{
results = context.MyTableQueryable
.Where(x => x.SomeColumn.EndsWith("sometext"))
.ToList();
}
// use results
However, this was not effective in practice. The problem seems to be that the column SomeColumn is not varchar, rather it's a char(31). This means that if a string is saved in the column that is less than 31 characters then there will be spaces added on the end of the string to ensure a length of 31 characters, and that fouls up the .EndsWith() query.
I used SQL Profiler to lookup the exact sql that was generated from the .EndsWith() method. Here is what I found:
--previous query code removed for brevity
WHERE [Extent1].[SomeColumn] LIKE N'%sometext'
So that is interesting. I'm not sure what the N means before '%sometext'. (I'll Google it later.) But I do know that if I take the same query and run it in SSMS without the N like this:
--previous query code removed for brevity
WHERE [Extent1].[SomeColumn] LIKE '%sometext'
Then the query works fine. Is there a way to get Linq and Entity Framework to drop that N from the query?
Please try this...
.Where(x => x.SomeColumn.Trim().EndsWith("sometext"))
Just spoke to my colleague who had a similar issue, see if the following works for you:
[Column(TypeName = "varchar")]
public string SomeColumn
{
get;
set;
}
Apparently setting the type on the column mapping will force the query to recognise it as a VARCHAR, where a string is normally interpreted as an NVARCHAR.

LINQ display row numbers

I simply want to include a row number against the returned results of my query.
I found the following post that describes what I am trying to achieve but gives me an exception
http://vaultofthoughts.net/LINQRowNumberColumn.aspx
"An expression tree may not contain an assignment operator"
In MS SQL I would just use the ROWNUMBER() function, I'm simply looking for the equivalent in LINQ.
Use AsEnumerable() to evaluate the final part of your query on the client, and in that final part add a counter column:
int rowNo = 0;
var results = (from data in db.Data
// Add any processing to be performed server side
select data)
.AsEnumerable()
.Select(d => new { Data = d, Count = ++rowNo });
I'm not sure whether LINQ to SQL supports it (but it propably will), but there's an overload to the Queryable.Select method that accepts an lambda with an indexer. You can write your query as follows:
db.Authors.Select((author, index) => new
{
Lp = index, Name = author.Name
});
UPDATE:
I ran a few tests, but unfortunately LINQ to SQL does not support this overload (both 3.5sp1 and 4.0). It throws a NotSupportedException with the message:
Unsupported overload used for query
operator 'Select'.
LINQ to SQL allows you to map a SQL function. While I've not tested this, I think this construct will work:
public partial class YourDataContext : DatContext
{
[Function(Name = "ROWNUMBER")]
public int RowNumber()
{
throw InvalidOperationException("Not called directly.");
}
}
And write a query as follows:
from author in db.Authors
select new { Lp = db.RowNumber(), Name = author.Name };

Linq to Nhiberate - Where clause

I have tried to find an answer to this, but could not find one in google. Probably not searching the correct terms, so thought I would ask here.
The following returns all my contacts, not the ones that equal the adjusterType sent in.
var contacts = from c in session.Linq<Contact>() select c;
contacts.Where(c => c.ContactAdjuster.AdjusterType == adjusterType);
The following does return the expected results. It does return only the contacts that meet the adjusterType. I believe it is my lack of understanding of LINQ.
var contacts = from c in session.Linq<Contact>() select c;
contacts = contacts.Where(c => c.ContactAdjuster.AdjusterType == adjusterType);
Thanks in advance.
the Where clause returns an IEnumerable in your case an IEnumerable. This is the standard LiNQ and C# behavior. Instead of modifying your collection it is returning a new collection based on your where clause.
I suppose NHibernate LiNQ should mimic this.
CatZ is absolutely right, you are not modifying the "contacts" collection/enumerable you are creating a new based on the existing, which is why your second statement works.
But instead of just repeating CatZ statement, here is a little add-on:
You can write this in one statement though
var contacts =
from c in session.Linq<Contact>()
where c.ContactAdjuster.AdjusterType == adjusterType
select c;
Or simply
var contacts = session.Linq<Contact>().Where(c => c.ContactAdjuster.AdjusterType == adjusterType);