Where clause not working with parantheses - nhibernate

Suppose the following Query using a NH 3.4 and RepositoryPattern
var list = _repository
.QueryOver()
.Where(x => (x.Age > 20)) // notice the parantheses
.Future()
.ToList();
Whith these parantheses added the NH is failing to work, and causes a SO exception.
If replacing .Where(x => (x.Age > 20)) with .Where(x => x.Age > 20)
it works as expected.
Any clues on why it doesn't work with extra parantheses?
Note
This is a simplified scenario from the bigger picture. In production i'm passing that .Where(...) through a parameter Expression<Func<Person, bool>> where

I have a doubt the error is there:
Expression<Func<MyClass, bool>> mc1 = x => (x.ID > 20);
Expression<Func<MyClass, bool>> mc2 = x => x.ID > 20;
var body1 = mc1.Body.NodeType; // GreatThan
var body2 = mc2.Body.NodeType; // GreatThan
The brackets are removed by the compiler. There is nothing in the Expression tree "language" (class system) to explicitly represent a bracket.

Related

could not resolve property: CS$<>8__locals2 of: BusinessObjectMain; Nhibernate QueryOver Linq, lambda expression inside a for loop

We use Vs2019 with an older version of Nhibernate 3.2.
We recently upgraded to VS2019. This code worked correctly in VS2010.
We are getting this error with Nibernate QueryOver, lambda expressions inside a where clause. I stripped it down to a minimal version. During runtime Block 1 fails and Block 2 does not. Why? I can't figure this out.
Could it be something related to .net framework 4.8, roselyn or an older version of Nhibernate?
I didn't post all the Business Objects here but if needed I can do so. I am also seeing this behavior across the application for different Business Objects.
could not resolve property: CS$<>8__locals2 of: BusinessObjectMain
Block 1
var EventQuery = session.QueryOver<BusinessObjectMain()
.JoinAlias(ce => ce.Bo1, () => Bo1)
.JoinAlias(ce => ce.Bo2, () => Bo2, JoinType.LeftOuterJoin)
.JoinAlias(() => Bo2.Bo3, () =>Bo3, JoinType.LeftOuterJoin)
;
for (int i = 0; i < 1; i++) {
object[] arr = new object[1];
arr[0] = 202;
EventQuery = EventQuery.Where(() => Bo3.TypeId.IsIn(arr));
}
var Results = EventQuery.Future<Data>();
var list = Results.ToList();---Error happens here. could not resolve property: CS$<>8__locals2 of: BusinessObjectMain
Why does the code below work? If I move the Where clause outside the for loop, it works correctly without any errors
Block 2
var EventQuery = session.QueryOver<BusinessObjectMain()
.JoinAlias(ce => ce.Bo1, () => Bo1)
.JoinAlias(ce => ce.Bo2, () => Bo2, JoinType.LeftOuterJoin)
.JoinAlias(() => Bo2.Bo3, () =>Bo3, JoinType.LeftOuterJoin)
;
object[] arr = new object[1];
arr[0] = 202;
EventQuery = EventQuery.Where(() => Bo3.TypeId.IsIn(arr));
for (int i = 0; i < 1; i++) {
}
var Results = EventQuery.Future<Data>();
var list = Results.ToList()
It's a bug in NHibernate related to Roslyn compiler that's used since Visual Studio 2015 (because Roslyn compiles lambda's differently). See https://nhibernate.jira.com/browse/NH-3795, https://github.com/nhibernate/nhibernate-core/pull/441
It's fixed in NHibernate versions 3.3.5, 3.4.1 and 4.0.4+

NHibernate Linq Query with Projection and Count error

I have the following query:
var list = repositoy.Query<MyClass>.Select(domain => new MyDto()
{
Id = domain.Id,
StringComma = string.Join(",", domain.MyList.Select(y => y.Name))
});
That works great:
list.ToList();
But if I try to get the Count I got an exception:
list.Count();
Exception
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException
A recognition error occurred. [.Count[MyDto](.Select[MyClass,MyDto](NHibernate.Linq.NhQueryable`1[MyClass], Quote((domain, ) => (new MyDto()domain.Iddomain.Name.Join(p1, .Select[MyListClass,System.String](domain.MyList, (y, ) => (y.Name), ), ))), ), )]
Any idea how to fix that without using ToList ?
The point is, that we should NOT call Count() over projection. So this will work
var query = repositoy.Query<MyClass>;
var list = query.Select(domain => new MyDto()
{
Id = domain.Id,
StringComma = string.Join(",", domain.MyList.Select(y => y.Name))
});
var count = query.Count();
When we use ICriteria query, the proper syntax would be
var criteria = ... // criteria, with WHERE, SELECT, ORDER BY...
// HERE cleaned up, just to contain WHERE clause
var totalCountCriteria = CriteriaTransformer.TransformToRowCount(criteria);
So, for Count - use the most simple query, i.e. containing the same JOINs and WHERE part
If you really don't need the results, but only the count, then you shouldn't even bother writing the .Select() clause. Radim's answer as posted is a good way to both get the results and the count, but if your database supports it, use future queries to execute both in the same roundtrip to the database:
var query = repository.Query<MyClass>;
var list = query.Select(domain => new MyDto()
{
Id = domain.Id,
StringComma = string.Join(",", domain.MyList.Select(y => y.Name))
}).ToFuture();
var countFuture = query.Count().ToFutureValue();
int actualCount = countFuture.Value; //queries are actually executed here
Note that there in NH prior to 3.3.3, this would still execute two round-trips (see https://nhibernate.jira.com/browse/NH-3184), but it would work, and if you ever upgrade NH, you get a (minor) performance boost.

NHibernate - Linq query using COUNT(DISTINCT)

I'm trying to get a paged query to work properly using LINQ and NHibernate. On a single table, this works perfect, but when more than one table is joined, it's causing me havoc. Here is what I have so far.
public virtual PagedList<Provider> GetPagedProviders(int startIndex, int count, System.Linq.Expressions.Expression<Func<Record, bool>> predicate) {
var firstResult = startIndex == 1 ? 0 : (startIndex - 1) * count;
var query = (from p in session.Query<Record>()
.Where(predicate)
select p.Provider);
var rowCount = query.Select(x => x.Id).Distinct().Count();
var pageOfItems = query.Distinct().Skip(firstResult).Take(count).ToList<Provider>();
return new PagedList<Provider>(pageOfItems, startIndex, count, rowCount);
}
There are a couple issues I'm having with this. First off, the rowCount variable is pulling back a COUNT(*) on a join that has one to many. So my count is way off of what is should be, I'm expecting 129, but getting back over 1K.
The second issue I'm happening is with the results. If I'm on the first page (firstResult = 0), then I'm getting correct results. However, if firstResult is something other than 0, it produces completely different SQL. Below is the SQL generated by both scenarios, I've trimmed out some of the fat to make it a little more readable.
--firstResult = 0
select distinct TOP (30) provider1_.Id as Id12_, provider1_.TaxID as TaxID12_,
provider1_.Facility_Name as Facility5_12_, provider1_.Last_name as Last6_12_,
provider1_.First_name as First7_12_, provider1_.MI as MI12_
from PPORecords record0_ left outer join PPOProviders provider1_ on record0_.Provider_id=provider1_.Id,
PPOProviders provider2_ where record0_.Provider_id=provider2_.Id
and provider2_.TaxID='000000000'
--firstResult = 30
SELECT TOP (30) Id12_, TaxID12_, Facility5_12_, Last6_12_, First7_12_, MI12_,
FROM (select distinct provider1_.Id as Id12_, provider1_.TaxID as TaxID12_,
provider1_.Facility_Name as Facility5_12_,
provider1_.Last_name as Last6_12_,
provider1_.First_name as First7_12_,
provider1_.MI as MI12_,
ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row
from PPORecords record0_ left outer join PPOProviders provider1_ on record0_.Provider_id=provider1_.Id,
PPOProviders provider2_
where record0_.Provider_id=provider2_.Id and provider2_.TaxID='000000000') as query
WHERE query.__hibernate_sort_row > 30
ORDER BY query.__hibernate_sort_row
The problem with the second query is the "distinct" keyword is not present on the outer query, only the inner query. Any ideas how to correct this?
Thanks for any suggestions!
[UPDATE]
This is the code that is building the the Linq query predicate.
private Expression<Func<Record, bool>> ParseQueryExpression(string Query) {
Expression<Func<Record, bool>> mExpression = x => true;
string[] splitQuery = Query.Split('|');
foreach (string query in splitQuery) {
if (string.IsNullOrEmpty(query))
continue;
int valStartIndex = query.IndexOf('(');
string variable = query.Substring(0, valStartIndex);
string value = query.Substring(valStartIndex + 1, query.IndexOf(')') - valStartIndex - 1);
switch (variable) {
case "tax":
mExpression = x => x.Provider.TaxID == value;
break;
case "net":
mExpression = Combine<Record>(mExpression, x => x.Network.Id == int.Parse(value));
break;
case "con":
mExpression = Combine<Record>(mExpression, x => x.Contract.Id == int.Parse(value));
break;
case "eff":
mExpression = Combine<Record>(mExpression, x => x.Effective_Date >= DateTime.Parse(value));
break;
case "trm":
mExpression = Combine<Record>(mExpression, x => x.Term_Date <= DateTime.Parse(value));
break;
case "pid":
mExpression = Combine<Record>(mExpression, x => x.Provider.Id == long.Parse(value));
break;
case "rid":
mExpression = Combine<Record>(mExpression, x => x.Rate.Id == int.Parse(value));
break;
}
}
return mExpression;
}
It seems as if the Linq provider has some "unexpected" behaviour there. I could reproduce the issue with the rowCount that you are describing and also encountered some issues when trying to fix it with a subquery.
If I understand your intentions correctly you basically want to have a paged list of Providers whose Records match certain criteria.
In that case I would suggest using a subquery. However, I tried implementing the subquery with the Query() method but it did not work. So I tried the QueryOver() method which worked flawlessly. In your case the desired queries would look like this:
Provider pAlias = null;
var query = session.QueryOver<Provider>(() => pAlias).WithSubquery
.WhereExists(QueryOver.Of<Record>()
.Where(predicate)
.And(r => r.Provider.Id == pAlias.Id)
.Select(r => r.Provider));
var rowCount = query.RowCount();
var pageOfItems = query.Skip(firstResult).Take(count).List<Provider>();
That way you do not have to struggle with the Distinct(). And as much as I personally like and use NHibernate.Linq, I suppose, if it does not work in a given situation, one should use something else that works.
Edit: Splitting the query into smaller units.
// the method ParseQueryExpression() does not need to be modified, the Expression should work like that
System.Linq.Expressions.Expression<Func<Record, bool>> predicate = x => x.Provider.TaxID == "000000000";
// Query to evaluate predicate
IQueryable<Record> queryId = session.Query<Record>().Where(predicate).Select(r => r.Provider);
// extracting the IDs to use in a subquery
List<int> idList = queryId.Select(p => p.Id).Distinct().ToList();
// total count of distinct Providers
int rowCount = idList.Count;
// new Query on Provider, using the distinct Ids
var query = session.Query<Provider>().Where(p => idList.Contains(p.Id));
// the List<Provider> to display on the page
var pageOfItems = query.Skip(firstResult).Take(count).ToList();
The problem here is that you have multiple queries and therefore multiple roundtrips to the db. Another problem apperas when you examine the generated sql. The subquery idList.Contains(p.Id) will generate an in-clause with a lot of parameters.
As you can see, these are NHibernate.Linq queries. That is because the new QueryOver feature is not a true Linq provider and does not work well with dynamic Linq expressions.
You could get around that limitation by using Detached QueryOvers. The drawback here is that you would have to modify your ParseQueryExpression() somehow, e.g. like that:
private QueryOver<Record> ParseQueryExpression(string Query)
{
Record rAlias = null;
var detachedQueryOver = QueryOver.Of<Record>(() => rAlias)
.JoinQueryOver(r => r.Provider)
.Where(() => rAlias.Provider.TaxID == "000000000")
.Select(x => x.Id);
// modify your method to match the return value
return detachedQueryOver;
}
// in your main method
var detachedQueryOver = QueryOver.Of<Record>()
.WithSubquery
.WhereProperty(r => r.Id
.In(this.ParseQueryExpression(...));
var queryOverList = session.QueryOver<Provider>()
.WithSubquery
.WhereProperty(x => x.Id)
.In(detachedQueryOver.Select(r => r.Provider.Id));
int rowCount = queryOverList.RowCount();
var pageOfItems = queryOverList.Skip(firstResult).Take(count).List();
Well, I hope you are not too confused by that.

Querying Raven with Where() only filters against the first 128 documents?

We're using Raven to validate logins so people can get into our site.
What we've found is that if you do this:
// Context is an IDocumentSession
Context.Query<UserModels>()
.SingleOrDefault(u => u.Email.ToLower() == email.ToLower());
The query only filters on the first 128 docs of the documents in
Raven. There are several thousand in our database, so unless your
email happens to be in that first 128 returned, you're out of luck.
None of the Raven samples code or any
sample code I've come across on the net performs any looping using
Skip() and Take() to iterate through the set.
Is this the desired behavior of Raven?
Is it the same behavior even if you use an advanced Lucene Query? ie; Do advanced queries behave any differently?
Is the solution below appropriate? Looks a little ugly. :P
My solution is to loop through the set of all documents until I
encounter a non null result, then I break and return .
public T SingleWithIndex(string indexName, Func<T, bool> where)
{
var pageIndex = 1;
const int pageSize = 1024;
RavenQueryStatistics stats;
var queryResults = Context.Query<T>(indexName)
.Statistics(out stats)
.Customize(x => x.WaitForNonStaleResults())
.Take(pageSize)
.Where(where).SingleOrDefault();
if (queryResults == null && stats.TotalResults > pageSize)
{
for (var i = 0; i < (stats.TotalResults / (pageIndex * pageSize)); i++)
{
queryResults = Context.Query<T>(indexName)
.Statistics(out stats)
.Customize(x => x.WaitForNonStaleResults())
.Skip(pageIndex * pageSize)
.Take(pageSize)
.Where(where).SingleOrDefault();
if (queryResults != null) break;
pageIndex++;
}
}
return queryResults;
}
EDIT:
Using the fix below is not passing query params to my RavenDB instance. Not sure why yet.
Context.Query<UserModels>()
.Where(u => u.Email == email)
.SingleOrDefault();
In the end I am using Advanced Lucene Syntax instead of linq queries and things are working as expected.
RavenDB does not understand SingleOrDefault, so it performs a query without the filter. Your condition is then executed on the result set, but per default Raven only returns the first 128 documents.
Instead, you have to call
Context.Query<UserModels>()
.Where(u => u.Email == email)
.SingleOrDefault();
so the filtering is done by RavenDB/Lucene.

LINQ & Lambda Expressions equivalent of SQL In

Is there a lambda equivalent of IN? I will like to select all the funds with ids either 4, 5 or 6. One way of writing it is:
List fundHistoricalPrices = lionContext.FundHistoricalPrices.Where(fhp => fhp.Fund.FundId == 5 || fhp.Fund.FundId == 6 || fhp.Fund.FundId == 7).ToList();
However, that quickly becomes unmanageable if I need it to match say 100 different fundIds. Can I do something like:
List
fundHistoricalPrices =
lionContext.FundHistoricalPrices.Where(fhp
=> fhp.Fund.FundId in(5,6,7)).ToList();
It's somewhere along these lines, but I can't quite agree with the approach you have taken. But this will do if you really want to do this:
.Where(fhp => new List<int>{5,6,7}.Contains( fhp.Fund.FundId )).ToList();
You may want to construct the List of ids before your LINQ query...
You can use the Contains() method on a collection to get the equivalent to in.
var fundIds = new [] { 5, 6, 7 };
var fundHistoricalPrices = lionContext.FundHistoricalPrices.Where(fhp => fundIds.Contains(fhp.Fund.FundId)).ToList();
You could write an extension method like this :
public static bool In<T>(this T source, params T[] list)
{
if(null==source) throw new ArgumentNullException("source");
return list.Contains(source);
}
Then :
List fundHistoricalPrices = lionContext.FundHistoricalPrices.Where(fhp => fhp.Fund.FundId.In(5,6,7)).ToList();
No, the only similar operator i'm aware of is the Contains() function.
ANother was is to construct your query dynamically by using the predicate builder out of the LINQkit: http://www.albahari.com/nutshell/predicatebuilder.aspx
Example
int[] fundIds = new int[] { 5,6,7};
var predicate = PredicateBuilder.False<FundHistoricalPrice>();
foreach (int id in fundIds)
{
int tmp = id;
predicate = predicate.Or (fhp => fhp.Fund.FundId == tmp);
}
var query = lionContext.FundHistoricalPrices.Where (predicate);