NHibernate accumulate queryOver conditions - nhibernate

I want to do a sort of filtering chain to filter Receipt objects using queryOver functionality.
The chain can differ in length, according to the parameters user chooses on the screen.
Eventually, I want the chain to run somehow like this:
public IList<Receipt> RunFilters()
{
IQueryOver<Receipt, Receipt> currQuery = NHibernateHelper.Session.QueryOver<Receipt>();
foreach (var item in filters)
{
currQuery = item.RunFilter(currQuery);
}
return currQuery.List();
}
So, the question is - how RunFilter should be defined? I thought it should be
public IQueryOver<Receipt, Receipt> RunFilter(IQueryOver<Receipt, Receipt> prevFilter)
and they I can do filters like
return prevFilter.Where(receipt => receipt.TotalSum > 0);
But I can't do
return prevFilter.JoinQueryOver(v => v.Store).Where(vv => vv.Name.Equals(m_storeName));
Any ideas?
Thanks in advance
Victor

return prevFilter.JoinQueryOver(v => v.Store).Where(vv => vv.Name.Equals(m_storeName));
the above can be written as
Store storeAlias = null;
return prevFilter.JoinAlias(v => v.Store, () => storeAlias).Where(() => storeAlias.Name == m_storeName);
EDIT: fixed equation

Related

Yii 2 can rewrite WHERE condition from model?

I have some where condition in my model .
Its check is field active or no.
Now I need to write a join relation. But I need to remove where condition. Is it possible?
My model.
...
public static function find() {
return (new AssetgroupsQuery(get_called_class()))->active();
}
My relation
public function getAssetgroup(): \app\models\AssetgroupsQuery {
return $this->hasOne(Assetgroups::class, ['asg_id' => 'ass_group'])->andOnCondition(['asg_active' => '1'])
->viaTable('assets', ['ass_id' => 'log_ass_id',]);
}
I need to got all active assets and join, if asset is empty I need to got null fields, but
model where condition added to my current sql query and remove all fields which assets are null.
I try to add some where Condition to remove old where, but it don't work.
Can you help me?
You can reset existing conditions by using where(null).
On relation level:
public function getAssetgroup(): \app\models\AssetgroupsQuery {
return $this->hasOne(Assetgroups::class, ['asg_id' => 'ass_group'])
->andOnCondition(['asg_active' => '1'])
->where(null)
->viaTable('assets', ['ass_id' => 'log_ass_id',]);
}
Or directly on join:
$query = MyModel::find()
->joinWith([
'assetgroup' => function (ActiveQuery $query) {
$query->where(null);
},
])

Nhibernate Restrictions.In Error

Finally tracked down my error which is a result of the query. I have an nhibernate query using a Restrictions.In. Problem is once query executes if no results returned query throws error immediately. Is there another restriction that I can use. I know if I was writing a linq query I could use the .Any to return bool value and go from there is there something similar I can do in this instance?
carMake is passed in
myQuery.JoinQueryOver(x => x.Car)
.Where(Restrictions.In("VIN",
Trades.Where(x => x.Car.Make.ToLower() == carMake.ToLower())
.Select(x => x.Car.PrimaryVIN)
.ToList()));
Assuming that Trades is a list of objects you can use .WhereRestrictionOn() instead. Try this (I split the code for better readability):
var vinsWithCarMake = Trades
.Where(x => x.Car.Make.ToLower() == carMake.ToLower())
.Select(x => x.Car.PrimaryVIN)
.ToList<string>();
var carAlias = null;
var result = myQuery.JoinAlias(x => x.Car, () => carAlias)
.WhereRestrictionOn(() => carAlias.VIN).IsInG<string>(vinsWithCarMake)
.List();

Use of Cast Projection inside a Concat Projection

I have the following query:
var result = _session.QueryOver<Entity>()
.Where(e => e.Property == value)
.SelectList(list => list
.Select(f => Projections.Concat("prefix-", e.BigIntProperty)).WithAlias(() => alias.Whatever)
...
)
.TransformUsing(Transformers.AliasToBean<Model>())
.Future<Model>();
The problem is that Projections.Concat() accepts only strings and since e.BigIntProperty is not, the above doesn't compile. Is there a way to cast e.BigIntProperty to string?
I tried something like the following, which doesn't work either:
.Select(f => Projections.Concat("prefix-", Projection.Cast(NHibernateUtil.String, e.BigIntProperty))).WithAlias(() => alias.Whatever)
, since Projections.Cast returns an IProjection and not a string.
Projections.Cast seems terribly limited in that it can't take arbitrary Projections. Luckily you can easily create your own custom projection that enables you to do that:
public static class CustomProjections
{
public static IProjection Concat(params IProjection[] projections)
{
return Projections.SqlFunction(
"concat",
NHibernateUtil.String,
projections);
}
}
Then, you'll be able to use your CustomProjections class like this:
var result = _session.QueryOver<Entity>()
.Where(e => e.Property == value)
.SelectList(list => list
.Select(CustomProjections.Concat(
Projections.Constant("prefix-"),
Projections.Cast(
NHibernateUtil.String,
Projections.Property<Entity>(e => e.BigIntProperty))))
.WithAlias(() => alias.Whatever)
...
)
.TransformUsing(Transformers.AliasToBean<Model>())
.Future<Model>();
I've already accepted Andrew's answer, but just for reference, you could use Projections.SqlFunction("concat", ...) directly which solves the whole issue since it can take IProjection's as arguments and not only string.
var result = _session.QueryOver<Entity>()
.Where(e => e.Property == value)
.SelectList(list => list
.Select(Projections.SqlFunction("concat",
NHibernateUtil.String,
Projections.Constant("prefix-"),
Projections.Cast(NHibernateUtil.String, Projections.Property<Entity>(e => e.BigIntProperty))))
.WithAlias(() => alias.Whatever)
...
)
.TransformUsing(Transformers.AliasToBean<Model>())
.Future<Model>();
NOTE: It seems that when calling either Projections.Concat(...) or Projections.SqlFunction("concat", ...), the query that is produced actually uses the + operator, e.g.:
SELECT (a + b) as foo FROM table
instead of:
SELECT concat(a, b) as foo FROM table
Of course, CONCAT is only available from MS SQL Server versions 2012 and above, so this is correct. Possibly the MsSQl2012Dialect could make use of the CONCAT, since CONCAT doesn't require that the arguments are varchar, they might as well be integers.
Unfortunately MsSQl2012Dialect doesn't do that, but it is very easy to build a custom Dialect:
public class CustomMsSql2012Dialect : MsSql2012Dialect
{
protected override void RegisterFunctions()
{
base.RegisterFunctions();
base.RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "concat(", ",", ")"));
}
}
So, if you use version 2012 or above and you declare the above as your Dialect, you can ditch the Projections.Cast(...) part

LINQ: Split Where OR conditions

So I have the following where conditions
sessions = sessions.Where(y => y.session.SESSION_DIVISION.Any(x => x.DIVISION.ToUpper().Contains(SearchContent)) ||
y.session.ROOM.ToUpper().Contains(SearchContent) ||
y.session.COURSE.ToUpper().Contains(SearchContent));
I want to split this into multiple lines based on whether a string is empty for example:
if (!String.IsNullOrEmpty(Division)) {
sessions = sessions.Where(y => y.session.SESSION_DIVISION.Any(x => x.DIVISION.ToUpper().Contains(SearchContent)));
}
if (!String.IsNullOrEmpty(Room)) {
// this shoudl be OR
sessions = sessions.Where(y => y.session.ROOM.ToUpper().Contains(SearchContent));
}
if (!String.IsNullOrEmpty(course)) {
// this shoudl be OR
sessions = sessions.Where(y => y.session.COURSE.ToUpper().Contains(SearchContent));
}
If you notice I want to add multiple OR conditions split based on whether the Room, course, and Division strings are empty or not.
There are a few ways to go about this:
Apply the "where" to the original query each time, and then Union() the resulting queries.
var queries = new List<IQueryable<Session>>();
if (!String.IsNullOrEmpty(Division)) {
queries.Add(sessions.Where(y => y.session.SESSION_DIVISION.Any(x => x.DIVISION.ToUpper().Contains(SearchContent))));
}
if (!String.IsNullOrEmpty(Room)) {
// this shoudl be OR
queries.Add(sessions.Where(y => y.session.ROOM.ToUpper().Contains(SearchContent)));
}
if (!String.IsNullOrEmpty(course)) {
// this shoudl be OR
queries.Add(sessions.Where(y => y.session.COURSE.ToUpper().Contains(SearchContent)));
}
sessions = queries.Aggregate(sessions.Where(y => false), (q1, q2) => q1.Union(q2));
Do Expression manipulation to merge the bodies of your lambda expressions together, joined by OrElse expressions. (Complicated unless you've already got libraries to help you: after joining the bodies, you also have to traverse the expression tree to replace the parameter expressions. It can get sticky. See this post for details.
Use a tool like PredicateBuilder to do #2 for you.
.Where() assumes logical AND and as far as I know, there's no out of box solution to do it. If you want to separate OR statements, you may want to look into using Predicate Builder or Dynamic Linq.
You can create an extension method to conditionally apply the filter:
public static IQueryable<T> WhereIf<T>(
this IQueryable<T> source, bool condition,
Expression<Func<T, bool>> predicate)
{
return condition ? source.Where(predicate) : source;
}
And use it like this:
using static System.String;
...
var res = sessions
.WhereIf(!IsNullOrEmpty(Division), y => y.session.SESSION_DIVISION.ToUpper().Contains(SearchContent))
.WhereIf(!IsNullOrEmpty(Room), y => y.session.ROOM.ToUpper().Contains(SearchContent))
.WhereIf(!IsNullOrEmpty(course), y => y.session.COURSE.ToUpper().Contains(SearchContent)));

NHibernate Multiquery for eager loading without joins

Is it possible to use a multiquery and have two hql queries returning two different sets of entities where one of the sets are used in the other and that the session "fixes" this via the first level cache?
E.g. scenario (a dumb one and it could be solved with joins)
public class Room
{
...
public virtual ISet<Bookings> Bookings {get;set;}
public virtual bool IsAvailible {get;set;}
...
}
public class Booking
{
...
}
After executing a multicriteria with two hql's:
returning all rooms where
IsAvailible = true
returning all bookings having a room that has a room that IsAvailible
when accessing a room from the result and its bookings I want them to be resolved from the second resultset via the firstlevel cache of the session and there by avoiding n+1.
Generally speaking, NHibernate can use the cache to "combine" the results from queries executed through Multiquery. However, it should be noted that this usually only applies to cases where lazy collections are loaded with no restrictions whatsoever.
Examples:
Invoice iAlias = null;
InvoiceDetails idAlias = null;
// Base-Query: get Invoices with certain condition
var invoices = session.QueryOver<Invoice>()
.Where(i => i.Number == "001")
.Future<Invoice>();
// Option 1: this will still cause N+1 if we iterate through invoices,
// because it doesn't know better
var invoicedetails = session.QueryOver<InvoiceDetails>()
.JoinAlias(a => a.Invoice, () => iAlias)
.Where(() => iAlias.Number == "001")
.Future<InvoiceDetails>();
// Option 2: this will still cause N+1 if we iterate through invoices,
// because we limited the possible results using a where-condition
var invoices2 = session.QueryOver<Invoice>()
.Left.JoinAlias(i => i.Details, () => idAlias)
.Where(i => i.Number == "001")
.And(() => idAlias.Quantity > 5)
.Future<Invoice>();
// Option 3: this will work without N+1, because we don't use a filter
// -> NHibernate will use the collection in cache
var invoices3 = session.QueryOver<Invoice>()
.Left.JoinAlias(i => i.Details, () => idAlias)
.Where(i => i.Number == "001")
.Future<Invoice>();
foreach (Invoice i in invoices)
{
int count = i.Details.Count;
}
If we comment out two of the three options and execute the code, we will see that only option 3 will prevent a N+1, the other two will still load the InvoiceDetails for each Invoice in the loop.
Of course this is a very simple example and it is obvious that Option 3 could also be executed without the Base-query and still return the same result, but I hope you get the idea.
In the case where we load two different sets of entities, i.e. the root class is different as in Option 1, this "combining" will most likely not work.
Sorry, if I used QueryOver instead of HQL, but the same rules apply.
Gyus, keep in mind that sometimes you can have similar problems because of
LeftOuterJoin is not set.
.JoinAlias(x => x.Prop, () => propAlias, JoinType.LeftOuterJoin)