How to construct a proper WHERE clause with RavenDb - ravendb

I have experimented with 2 forms of the call, this one
products = DocumentSession.Query<Product>()
.Statistics(out stats)
.Where(p => p.INFO2.StartsWith(term1))
.Where(p => p.INFO2.StartsWith(term2))
.Where(p => p.INFO2.StartsWith(term3))
.OrderByField(columnToSortBy, columnToSortByAsc)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToList()
;
and this way
products = DocumentSession.Query<Product>()
.Statistics(out stats)
.Where(p => p.INFO2.StartsWith(term1) & p.INFO2.StartsWith(term2) & p.INFO2.StartsWith(term3))
.OrderByField(columnToSortBy, columnToSortByAsc)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToList()
;
The first one returns records that are more in-line with my expectations, while the seconds seems to return ALL documents of type Product. What are the differences between the 2 from a LINQ expression point of view, and have I overlooked anything that might negate what I am trying to accomplish, which is a 3 term query and each term being AND'd together.
EDIT: revised code per Russ.
string t1 = terms[0];
string t2 = terms[1];
string t3 = terms[2];
products = DocumentSession.Query<Product>()
.Statistics(out stats)
.Where(p => p.INFO2.StartsWith(t1) && p.INFO2.StartsWith(t2) && p.INFO2.StartsWith(t3))
.OrderByField(columnToSortBy, columnToSortByAsc)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToList()
;
EDIT 2: This is where you smash your face down on the keyboard, or any other solid object for that matter... Gotta get back to the basic here with standard C# And and Or
Thank you,
Stephen

In the second block you're doing an & instead of an && so instead of being an AND comparison, it's trying to do a bitwise operation.
Edit: in the 2nd case, you can change this:
.Where(p => p.INFO2.StartsWith(terms[0]) & p.INFO2.StartsWith(terms[1]) & p.INFO2.StartsWith(terms[2]))
to this:
.Where(p => p.INFO2.StartsWith(terms[0]) && p.INFO2.StartsWith(terms[1]) && p.INFO2.StartsWith(terms[2]))
Which makes it proper AND clause.
2nd edit: If this is meant to be an AND operation, then you don't need 3 terms - you need a single term, otherwise you'll be comparing against 3 instances of the same string.
terms[0] = "test";
terms[1] = "test";
terms[2] = "test";
.Where(p => p.INFO2.StartsWith(terms[0]) && p.INFO2.StartsWith(terms[1]) && p.INFO2.StartsWith(terms[2]))
Is the same as
string term = "test";
.Where(p => p.INFO2.StartsWith(term) && p.INFO2.StartsWith(term) && p.INFO2.StartsWith(term))
Just mentioning this as this may make your code harder to maintain in the future.

Related

query did not return a unique result: 2 . Issue with Nhibernate Query

I am current having an issue with getting records from database using nhibernate.
My 'publicstring' column is having two values, one with lower case and the other with upper case.
I am trying to fetch one of them depending on what is typed.
Below is my query
private string _publicId;
var _mediaFileShare = this.Session.QueryOver<MediaFileShare>()
.Where(q => q.Publicsting == _publicstring)
.SingleOrDefault();
Answers would be appreciated..
A naive solution is:
var _mediaFileShare = this.Session.QueryOver<MediaFileShare>()
.Where(q => q.PublicString == _publicString)
.List()
.SingleOrDefault(r => string.Compare(r.PublicString, _publicString, StringComparison.Ordinal) == 0);
If you're confident that the query will always return two rows then performance should be very acceptable. Alternatively you could use a dynamic order by, something like:
var query = this.Session.QueryOver<MediaFileShare>()
.Where(q => q.PublicString == _publicString);
query = _publicString == _publicString.ToLower()
? query.OrderBy(q => q.PublicString)
: query.OrderByDescending(q => q.PublicString);
var _mediaFileShare = query.Take(1).SingleOrDefault();
The results using an order by will be dependent on your database and its collation setting.

Duplicated and unnecessary joins when using Linq in NHibernate

Basically I crossed the same problem of Linq provider in this linq-to-nhibernate-produces-unnecessary-joins
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = compset.A.B.CurrentSeason,
StartDate = compset.StartDate
}
)).ToList();
Which leads to duplicated join in its generated SQL
SELECT fwbcompeti0_.competitionsetid AS col_0_0_,
fwbcompeti0_.name AS col_1_0_,
fwbcompeti0_.DESCRIPTION AS col_2_0_,
fwbcompeti0_.area AS col_3_0_,
fwbcompeti0_.TYPE AS col_4_0_,
fwbseason3_.currentseason AS col_5_0_,
fwbcompeti0_.startdate AS col_6_0_
FROM fwbcompetitionset fwbcompeti0_
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
INNER JOIN B fwbseason3_
ON fwbcompeti2_.seasonid = fwbseason3_.seasonid
WHERE fwbcompeti0_.headline = #p0
AND fwbseason3_.currentseason = #p1
Notice these joins, which are totally duplicated and also affect my SQL Server's performence.
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
Update1
In the NHibernate 3.2, this LiNQ bug is still valid, and I could not find a simple and reasonable Linq solution.
So I used QueryOver + JoinAlias + TransformUsing finishing the job, workds perfect to me.
FWBCompetitionSet compset = null;
FWBCompetitionSeason compseason = null;
FWBSeason season = null;
IList<Competitions> dtoCompetitions;
dtoCompetitions = session.QueryOver<FWBCompetitionSet>(() => compset)
.JoinAlias(() => compset.FWBCompetitionSeason, () => compseason)
.JoinAlias(() => compseason.FWBSeason, () => season)
.Where(() => compset.HeadLine == true)
.And(() => season.CurrentSeason == true)
.SelectList(
list => list
.Select(c => c.CompetitionSetID).WithAlias(() => compset.CompetitionSetID)
.Select(c => c.Name).WithAlias(() => compset.Name)
.Select(c => c.Description).WithAlias(() => compset.Description)
.Select(c => c.Area).WithAlias(() => compset.Area)
.Select(c => c.Type).WithAlias(() => compset.Type)
.Select(c => season.CurrentSeason).WithAlias(() => season.CurrentSeason)
.Select(c => c.StartDate).WithAlias(() => compset.StartDate)
)
.TransformUsing(Transformers.AliasToBean<Competitions>())
.List<Competitions>();
Yet Another Edit:
I think I finally found out what's going on. It seems that the LINQ to NHibernate provider has trouble navigating associations from the target to the source table and generates a separate join each time it encounters such an association.
Since you don't provide your mapping, I used the mapping from linq-to-nhibernate-produces-unnecessary-joins. This model has a Document with one Job and many TranslationUnits. Each TranslationUnit has many Translation entities.
When you try to find a Translation based on a Job, you are traversing the associations in the reverse order and the LINQ provider generates multiple joins: one for Translation -> TranslationUnit and one for TranslationUnit to Document.
This query will generate redundant joins:
session.Query<TmTranslation>()
.Where(x => x.TranslationUnit.Document.Job == job)
.OrderBy(x => x.Id)
.ToList();
If you reverse the navigation order to Document -> TranslationUnit -> Translation, you get a query that doesn't produce any redundant joins:
var items=(from doc in session.Query<Document>()
from tu in doc.TranslationUnits
from translation in tu.Translations
where doc.Job ==job
orderby translation.Id
select translation).ToList();
Given this quirkiness, QueryOver seems like a better option.
Previous Edit:
I suspect the culprit is compset.A.B.CurrentSeason. The first joined table (fwbcompeti1_) returns A.B while the next two (fwbcompeti2_ and fwbseason3_) are used to return A.B. The LINQ to NHibernate provider doesn't seem to guess that A is not used anywhere else and fails to remove it from the generated statement.
Try to help the optimizer a little by replacing CurrentSeason = compset.A.B.CurrentSeason with CurrentSeason = true from the select, since your where statement returns only items with CurrentSeason == true.
EDIT: What I mean is to change the query like this:
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = true,
StartDate = compset.StartDate
}
)).ToList();
I simply replace the value compset.A.B.CurrentSeason with true

NHibernate uses value from the first query

So, I have a query like
public static IEnumerable<Archive> GetArchivesRecursive(this ISession session, Page rootPage)
{
var archives = session.Query<Page>().Where(p => p != rootPage && p.Path.StartsWith(rootPage.Path))
.GroupBy(p => new { Year = p.Published.Year, Month = p.Published.Month })
.Select(g => new Archive
{
ContextPageId = rootPage.Id,
Year = g.Key.Year,
Month = g.Key.Month,
TotalPageCount = g.Count(),
PublicPageCount = g.Count(p => p.State == PageState.Public && p.Published <= DateTime.UtcNow)
})
.ToList();
// ContextPageId has old value (id of the first rootPage used since app start)
// Why do I have to do this?
archives.ForEach(a => a.ContextPageId = rootPage.Id);
return archives;
}
For some reason ContextPageId property gets value of the first rootPage parameter that was used.
Well, quite interesting, my NH 3.2 actually fails with MismatchedTreeNodeException for even simplier queries when trying to have value from input in Select inside query. Which version are you using?
Anyway, looks like you just can't use values from outside the query in projection (Select) and this is probably NHibernate's Linq limitation. Your version seems to cache the compiled expression from Select ignoring the fact that it depends from a variable. DateTime value is the same for all calls, too, isn't it?
A bit cleaner workaround could go like this:
.Select(g => new
{
Year = g.Key.Year,
Month = g.Key.Month,
TotalPageCount = g.Count(),
PublicPageCount = g.Count(p => p.State == PageState.Public && p.Published <= DateTime.UtcNow)
})
.AsEnumerable()
.Select(g => new Archive
{
ContextPageId = rootPage.Id,
Year = g.Year,
Month = g.Month,
TotalPageCount = g.TotalPageCount,
PublicPageCount = g.PublicPageCount
})
.ToList();
EDIT I've looked a bit more carefully and this indeed is a NHibernate bug, already known. See this blog post and this JIRA bug entry.

NHibernate Linq where clause: value in collection

I am wondering can I do a where clause that takes in a collection?
List<string> myStrings = new List<strings> {"1", "2"};
session.Query<Table>().Where(x => x.Id == myStrings).ToList();
I basically want to get all rows from my db table that match everything in that query.
session.Query<Table>().Where(x => x.Id == myStrings[0]).ToList();
session.Query<Table>().Where(x => x.Id == myStrings[1]).ToList();
session.Query<Table>().Where(x => x.Id == myStrings[N]).ToList();
So thats what I would have to do right now. I would probably through that in a for loop but that is alot of queryies and I rather just do one query.
Or do I have to use the nhibernate create query syntax
var query = "Select * From Where In (:Id)";
session.CreateQuery(query)SetParameter("Id",myStrings) // not sure if I have to something like .ExecuteUpdate(); but just for select instead
session.Query<Table>().Where(x => myStrings.Contains(s => x.Id));
session.Query<Table>().Where(x => myString.All(s => x.Id == s));
you should use Any or All extension method on your collection

NHibernate Linq - how to create a where statement with IS NOT NULL

how can i achieve this query with Nhibernate Linq?
var l = session.CreateQuery("from Auswahl a where a.Returnkey is not null").List<Auswahl>();
i tried this but it always returns an empty list.
var l = session.Linq<Auswahl>()
.Where(item => !String.IsNullOrEmpty(item.Returnkey))
.Select(item => item)
.ToList();
Have you tried:
var l = session.Linq<Auswahl>()
.Where(item => item.Returnkey != null && item.Returnkey != "")
.Select(item => item)
.ToList();
I'm not sure that using String.IsNullOrEmpty would work, also it checks for two conditions - if it's NULL and if it's a blank empty string, how would that get translated into SQL? Might be worth having a look at SQL Profiler to see the raw SQL query it generates.