I have two models: Thing and ThingStatus. Thing has an Id and some other fields. ThingStatus is a model which stores Status enum corresponding to id of Thing. Now I want to fetch Things that have Status != Completed.
What I try to do now looks like this:
var unfinishedIds = session.QueryOver<ThingStatus>()
.Where(t => t.Status != StatusEnum.Completed)
.Select(t => t.Id)
.List<long>()
.ToArray();
var unfinishedThings = session.QueryOver<Thing>()
.WhereRestriction(t => t.Id)
.IsIn(unfinishedIds)
.List<Thing>();
As far as I understand, in such case unfinishedIds will be fetched from database and only after that used as a filter in unfinishedThings query. Is there any way to avoid that and have the query optimizer select the right way to do that? I've heard there are some futures available with nhibernate but I'm not sure how they'd help here.
You can use a subquery if you can't create a NHibernate relationship between the two entities. No relationship --> no JoinAlias (or JoinQueryOver) possible.
With a subquery:
var unfinishedIds = QueryOver.Of<ThingStatus>()
.Where(t => t.Status != StatusEnum.Completed)
.Select(t => t.Id);
var unfinishedThings = session.QueryOver<Thing>()
.WithSubquery.WhereProperty(t => t.Id).In(unfinishedIds)
.List<Thing>();
(note the use of QueryOver.Of<>)
The query is equivalent to writing:
SELECT * FROM Things WHERE Id IN (SELECT Id FROM ThingsStatuses WHERE Status <> 'Completed')
Related
I am trying to filter out the second part of the tables (UserRoles.IsDeleted==false). Is there any advice how i can do that?
var Users = context.Users.Where(r => r.IsDeleted == IsDeleted).ToList<User>();
Users = context.Users.Include(x => x.UserRoles.Select(y=>y.IsDeleted==false)).ToList();
Thank you
You can do the following to filter using the second part:
var Users = context.Users.Where(r => r.IsDeleted == IsDeleted).ToList<User>();
if(condition)
{
Users = Users.where(y => y.IsDeleted == false)).ToList();
}
There are two options to filter related entities
Doing a projection.
Unfortunately, when you use Include method, you can't filter the related entities as you intend to do. You need to project your query to a DTO object or a anonymous object, as the below example.
var query=context.Users.Include(x => x.UserRoles)
.Where(r => r.IsDeleted == IsDeleted)
.Select(u=> new{ ...,
Roles=x => x.UserRoles.Where(y=>!y.IsDeleted)})
A second option could be using Explicitly Loading. But this is in case you can load the related entities of one specific entity,eg,.
var user=context.Users.FirstOrDefault(r.IsDeleted == IsDeleted);//Getting a user
context.Entry(user)
.Collection(b => b.UserRoles)
.Query()
.Where(y=>!y.IsDeleted)
.Load();
You can do this inside of a foreach per each entity you get from the first query,
var query=context.Users.Where(r => r.IsDeleted == IsDeleted);
foreach(var u in query)
{
context.Entry(u)
.Collection(b => b.UserRoles)
.Query()
.Where(y=>!y.IsDeleted)
.Load();
}
but it's going to be really inefficient because you are going to do a roundtrip to your DB per each entity. My advice is use the first option, projecting the query.
I've got a table
Application
ApplicationID,
NAme
ApplicationSteps
AplicationStepID,
AplicationID,
StepID
ApplicationStepCriterias
ApplicationStepID,
CriteriaID
So I've got one SelectedCriteriaID - a user choose from a dropdown one criteria and he wants all the applications which has this SelectedCriteriaID in the table ApplicationStepCriterias
I tried
var ds = context.Applications
.Where(a => a.ApplicationSteps
.Select(x=>x.ApplicationStepCriterias
.Select(t=>t.CriteriaId))
.Contains(SelectesdCriteria));
But as I have as result IEnumerable<IEnumerable<int>> I cannot use Contains
Just I get a list of all the CriteriaIds for each ApplicationStep(also a sequence). Just I cannot think of way to get in one list all the CriteriIds.
First, let me try to get the names right. This is not a pure many-to-many association, because the junction class is part of the class model. It is what I unofficially call a 1-n-1 association. So you have
Application -< ApplicationSteps >- ApplicationStepCriterias
I'd strongly recommend to use singular names for your classes ...
Application -< ApplicationStep >- ApplicationStepCriterion
... so you can use plural for collection property names without getting confused.
If I'm right so far, you query should be
context.Applications
.Where(a => a.ApplicationSteps
.Any(x => selectedCriteria
.Contains(x.ApplicationStepCriterion.CriteriaId));
(and I'd also prefer CriterionId, probably referring to a Criterion class)
You may try something like this:
var applicationStepIds = context.ApplicationStepCriterias
.Where(i => i.CriteriaID == selectedCriteria)
.Select(i => i.ApplicationStepID)
.Distinct();
var applicationIds = context.ApplicationSteps
.Where(i => applicationStepIds.Contains(i.AplicationStepID))
.Select(i => i.AplicationID)
.Distinct();
var result = context.Applications.Where(i => applicationIds.Contains(i.ApplicationId));
I have the following nHibernate query that works well for me to pull an entity and eager fetch its sub collections.
var contactInfo = session.QueryOver<PeopleInfo>()
.Fetch(x => x.Addresses).Eager
.Fetch(x => x.EmailAddresses).Eager
.Fetch(x => x.PhoneNumbers).Eager
.Where(x => x.Id == contactInfoId)
.SingleOrDefault();
Now, let's say in Addresses that there is a field called Active,
is there a way to just return all the active addresses in one call?
I can filter it out after the fact, I'm just wondering if there's
a way to do it via query over.
Thanks!
Yes, you can do this, but you need to include some joins in your QueryOver query.
Under the hood, .Fetch is generating a bunch of LEFT JOINs to bring back the full list of PeopleInfo without excluding people that don't have any of the associated collections that you're fetching eagerly.
You can override the way the join is performed by performing a left join yourself.
For example, if you want to get all PeopleInfo, and only addresses that are Active, you could do the following:
Address addressAlias;
var addressRestriction = Restrictions.Where(() => address.Active);
session.QueryOver<PeopleInfo>()
.Left.JoinQueryOver(
pi => pi.Addresses, () => addressAlias, addressRestriction)
.Fetch(x => x.Addresses).Eager
.Fetch(x => x.EmailAddresses).Eager
.Fetch(x => x.PhoneNumbers).Eager
.Where(x => x.Id == contactInfoId)
.SingleOrDefault();
Now, since you're JOINing on Address and selecting out the entire PeopleInfo entity, you don't actually need to use .Fetch to pull back all of the Address fields.
session.QueryOver<PeopleInfo>()
.Left.JoinQueryOver(
x => x.Addresses, () => addressAlias, addressRestriction)
.Fetch(x => x.EmailAddresses).Eager
.Fetch(x => x.PhoneNumbers).Eager
.Where(x => x.Id == contactInfoId)
.SingleOrDefault();
The Address columns are included in the SELECT clause because you've joined on Address and are selecting out the entire PeopleInfo class.
These both generate the same SQL, but you should verify in a profiler. It should look something like this:
SELECT this_.* -- All PeopleInfo columns
addressali1_.*, -- All Address columns
emailaddre4_.* -- All email address columns
FROM PeopleInfo this_
LEFT OUTER JOIN address addressali1_
ON this_.id = addressali1_.personid
AND ( addressali1_.active = 1)
LEFT OUTER JOIN emailaddress emailaddre4_
ON this_.id = emailaddre4_.personid
WHERE this_.id = <your id>
i want to build a query that will select some columns from a joined table (many to one relationship in my data model).
var q = ses.QueryOver<Task>().Select(x => x.Id, x => x.CreatedDate, x => x.AssigneeGroup.Name, x => x.AssigneeGroup.IsProcessGroup);
Here i'm retrieving properties from AssigneeGroup which is a reference to another table, specified in my mapping. But when I try to run this query I get
Exception: could not resolve property: AssigneeGroup.Name of: Task
So it looks like NHibernate is not able to follow relations defined in my mapping and doesn't know that in order to resolve AssigneeGroup.Name we should do a join from 'Task' to 'Groups' table and retrieve Group.Name column.
So, my question is, how to build such queries? I have this expression: x => x.AssigneeGroup.Name, how to convert it to proper Criteria, Projections and Aliases? Or is there a way to do this automatically? It should be possible because NHibernate has all the information...
Your query need association and should look like this:
// firstly we need to get an alias for "AssigneeGroup", to be used later
AssigneeGroup assigneeGroup = null;
var q = ses
.QueryOver<Task>()
// now we will join the alias
.JoinAlias(x => x.AssigneeGroup, () => assigneeGroup)
.Select(x => x.Id
, x => x.CreatedDate
// instead of these
// , x => x.AssigneeGroup.Name
// , x => x.AssigneeGroup.IsProcessGroup
// use alias for SELECT/projection (i.e. ignore "x", use assigneeGroup)
, x => assigneeGroup.Name
, x => assigneeGroup.IsProcessGroup
);
More and interesting reading:
NHibernate - CreateCriteria vs CreateAlias, to get more understanding when to use JoinAlias (CreateAlias) and when JoinQueryOver (CreateCriteria)
Criteria API for: 15.4. Associations
QueryOver API for 16.4. Associations
You have to join the two tables if you wish to select columns from something other than the root table/entity (Task in our case).
Here is an example:
IQueryOver<Cat,Kitten> catQuery =
session.QueryOver<Cat>()
.JoinQueryOver<Kitten>(c => c.Kittens)
.Where(k => k.Name == "Tiddles");
or
Cat catAlias = null;
Kitten kittenAlias = null;
IQueryOver<Cat,Cat> catQuery =
session.QueryOver<Cat>(() => catAlias)
.JoinAlias(() => catAlias.Kittens, () => kittenAlias)
.Where(() => catAlias.Age > 5)
.And(() => kittenAlias.Name == "Tiddles");
Alternatively you could use the nhibernate linq provider (nh > 3.0):
var q = ses.Query<Task>()
.Select(x => new
{
Id = x.Id,
CreatedDate = x.CreatedDate,
Name = x.AssigneeGroup.Name,
IsProcessGroup = x.AssigneeGroup.IsProcessGroup
});
I am using NHibernate and while traversing my code I came upon two functions that are called in sequence. They are probably a school example of 1) extra database round trip and 2) in-memory processing at the application side. The code involved is:
// 1) Getting the surveys in advance
var surveys = DatabaseServices.Session.QueryOver<Survey>()
.Where(x => x.AboutCompany.IsIn(companyAccounts.ToList()))
// Actual query that can be optimized
var unverifiedSurveys = DatabaseServices.Session.QueryOver<QuestionInSurvey>()
.Where(x => x.Survey.IsIn(surveys.ToList()))
.And(x => x.ApprovalStatus == status)
.List();
// 2) In-memory processing
return unverifiedSurveys.Select(x => x.Survey).Distinct()
.OrderByDescending(m => m.CreatedOn).ToList();
I have read that the actual Distinct() operation with the QueryOver API can be done using 1 .TransformUsing(Transformers.DistinctRootEntity)
Could anyone give an example how the queries can be combined thus having one round trip to the database and no application-side processing?
The most suitable way in this scenario is to use Subselect. We will firstly create the detached query (which will be executed as a part of main query)
Survey survey = null;
QueryOver<Survey> surveys = QueryOver.Of<Survey>(() => survey)
.Where(() => survey.AboutCompany.IsIn(companyAccounts.ToList()))
.Select(Projections.Distinct(Projections.Property(() => survey.ID)));
So, what we have now is a statement, which will return the inner select. Now the main query:
QuestionInSurvey question = null;
var query = session.QueryOver<QuestionInSurvey>(() => question)
.WithSubquery
.WhereProperty(() => qeustion.Survey.ID)
.In(subQuery) // here we will fitler the results
.And(() => question.ApprovalStatus == status)
.List();
And what we get is the:
SELECT ...
FROM QuestionInSurvey
WHERE SurveyId IN (SELECT SurveyID FROM Survey ...)
So, in one trip to DB we will recieve all the data, which will be completely filtered on DB side... so we are provided with "distinct" set of values, which could be even paged (Take(), Skip())
This might be something like this, which requires a distinct projection of all properties of Survey. I guess there is a better solution, but can not get to it ;-) Hope this will help anyway.
Survey surveyAlias = null;
var result =
session.QueryOver<QuestionInSurvey>()
.JoinAlias(x => x.Survey, () => surveyAlias)
.WhereRestrictionOn(() => surveyAlias.AboutCompany).IsIn(companyAccounts.ToList())
.And(x => x.ApprovalStatus == status)
.Select(
Projections.Distinct(
Projections.ProjectionList()
.Add(Projections.Property(() => surveyAlias.Id))
.Add(Projections.Property(() => surveyAlias.AboutCompany))
.Add(Projections.Property(() => surveyAlias.CreatedOn))
)
)
.OrderBy(Projections.Property(() => surveyAlias.CreatedOn)).Desc
.TransformUsing(Transformers.AliasToBean<Survey>())
.List<Survey>();