I am stuck with a SQL query (using NHibernate 4).
I have 2 tables (Client and Technology) with many-to-many relationship so I created a junction table called ClientTechnology.
I am trying to retrieve all the Technologies available (that are non-custom) PLUS all the Technologies available (that are custom) and belong to a given Client.
In SQL this is the statement:
declare #clientId int = 1
select * from
[dbo].[Technology] t
where t.IsCustom = 0
union
select t.* from
[dbo].[Technology] t
join [dbo].[ClientTechnology] ct
on ct.TechnologyId = t.Id
where t.IsCustom = 1 and ct.ClientId = #clientId
My Fluent Mapping for Client table is:
public ClientMap()
{
Id(x => x.Id);
Map(x => x.Name).Not.Nullable();
}
For Technology table is:
public TechnologyMap()
{
Id(x => x.Id);
Map(x => x.Name).Not.Nullable();
Map(x => x.IsCustom).Not.Nullable();
HasMany(x => x.ClientTechnologies)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.Table("ClientTechnology")
.KeyColumn("TechnologyId");
}
and finally the junction table ClientTechnology:
public ClientTechnologyMap()
{
Id(x => x.Id);
Map(x => x.Alias).Not.Nullable();
Map(x => x.IsDeleted).Not.Nullable();
References<Client>(x => x.Client, "ClientId");
References<Technology>(x => x.Technology, "TechnologyId");
}
I a open to different options to achieve this.
Assuming I have available a Client object (the ClientId)
I could retrieve first a list of Technologies that match the requirement IsCustom = false
and then retrieve a list of Technologies that match the requirement
IsCustom = true AND "the provided client is the owner of this custom technology"
Within a method public IEnumerable<Technology> GetTechnologies(Client client) that must return the enumerable of Technology (given a Client instance)
I have tried the following to retrieve globalTechnologies:
var globalTechnologies = _session.QueryOver<Technology>()
.WhereNot(x => x.IsDeleted)
.WhereNot(x => x.IsCustom)
.List();
And the following for customTechnologies whose owner is the client:
Technology technology = null;
ClientTechnology clientTechnology = null;
var customTechnologies = _session.QueryOver<Technology>(() => technology)
.JoinAlias(() => technology.ClientTechnologies, () => clientTechnology)
.WhereNot(x => x.IsDeleted)
.Where(x => x.IsCustom)
.Where(clientTechnology.Client == client) //this doesn't compile
.List();
but I don't know how to access the junction table (joined) in order to apply the restriction.
Any help would be much appreciated. Thank you.
In your case, the only problem is, that you do not provide Expression inside of the .Where(), so this should do the job:
// instead of this
// .Where(clientTechnology.Client == client) //this doesn't compile
// use this
.Where(() => clientTechnology.Client == client)
But I would go even farther. We should be able to creat subquery, which
will return only such Techonology.Id which are belonging to client.
we then can use also OR and have one query which would either select these who are:
NOT IsCustom or
Do belong to Client
How to create subquery you can see here:
How to do a QueryOver in Nhibernate on child collection
And example with OR
Use OR Clause in queryover in NHibernate
NHibernate QueryOver with WhereRestriction as OR
Related
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')
I am using NHibernate with mapping by code.
I have three models: Solution, Installation and System. There are one-to-many relations between them. So that each Solution has a list of Installations, and each Installation has a list of Systems.
Each system has a property "Type", which can be "1" or "0".
I am trying to write a method in the Solution repository that will return all the Solutions, with their Installations with only the Systems of type "1".
I have tried the Where-keyword in the SystemMap but i get the same result with and without it. Then i tried a few different experiments with QueryOver(???) without success.
How do i go about to filter on information in the last node?
Thank to your answer, i have done the following implementation, but it results in a huge amount of Systems and Solutions. Maybe i have done something wrong?
The Maps are as follows:
public SAPSolutionMap()
{
Id(t => t.YPID);
Property(e => e.ShortName);
Property(e => e.FullName);
Bag(x => x.SapInstallations, colmap =>
{
colmap.Table("SAPInstallation");
colmap.Key(x => x.Column("Solution"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
colmap.Cascade(Cascade.None);
}, map => map.OneToMany(m => m.Class(typeof(SAPInstallation))));
}
public SAPInstallationMap()
{
Id(t => t.InstallationNumber);
Bag(x => x.SapSystems, colmap =>
{
colmap.Table("sapgui");
colmap.Key(x => x.Column("Installation"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Cascade(Cascade.None);
colmap.Fetch(CollectionFetchMode.Join);
//colmap.Where("Type = 1");
}, map => map.OneToMany(m => m.Class(typeof(SAPSystem))));
ManyToOne(x => x.SapSolution, map =>
{
map.Column("Solution");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPSolution));
});
}
public SAPSystemMap()
{
Id(t => t.ID, t => t.Generator(Generators.Identity));
Property(e => e.Type);
Property(e => e.ExplanationText);
ManyToOne(x => x.SapInstallation, map =>
{
map.Column("Installation");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPInstallation));
});
}
And the Query:
public IList<SAPSolution> GetProductionSystems()
{
SAPSystem syst = null;
SAPInstallation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.SapInstallation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.SapSolution.YPID);
// main Query
var query = Session.QueryOver<SAPSolution>()
.WithSubquery
.WhereProperty(root => root.YPID)
.In(subquery);
return query.List<SAPSolution>();
}
Thank you!
General solution should be:
// this is a subquery (SELECT ....
System syst = null;
Installation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.Installation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.Solution.ID)
;
// main Query
var query = session.QueryOver<Solution>()
.WithSubquery
.WhereProperty(root => root.ID)
.In(subquery)
;
var list = query
.Take(10)
.Skip(10)
.List<Solution>();
What we can see, that Solution, Installation and System
System has property Installation (many-to-one)
Installation has property Solution (many-to-one)
This is expect-able, because it goes side by side with one-to-many (it is the reverse mapping)
So, then we create subquery, which returns just solution ID's which belong to system with searched Type.
Main query is flat (the great benefit) and we can use paging on top of it.
We would be able to do that even if there is only one way (one-to-many). But that will generate more complicated SQL query ... and does not make sense. In C# we can have both relations...
EXTEND:
You did a great job. Your mapping and query is really cool. But there is one big but: LAZY is what we should/MUST use. Check this:
NHibernate is lazy, just live with it, by Ayende
So, our, collections cannot be FETCHING with a JOIN, because that will multiply the result (10 solutions * 100 installation * 10 systems == 10000 results)
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
We should use LAZY as possible. To avoid later 1 + N issue, we can use batch-fetching (for example check this)
How to Eager Load Associations without duplication in NHibernate?
So, our collections should be mapped like this:
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.Lazy);
colmap.BatchSize(100);
With this setting, the query will really use only the root object and related collections will be loaded very effectively
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>();
I got the following error message from nhibernate:
{"not an association: ID"}
Model.Order orderAlias = null;
Model.Unit unitAlias = null;
query to reproduce:
var query = m_hibernateSession.QueryOver<Model.Order>(() => orderAlias)
.JoinAlias(() => orderAlias.ID, () => unitAlias, JoinType.InnerJoin)
.TransformUsing(Transformers.DistinctRootEntity)
.OrderBy(x => x.PONumber).Desc.Take(5);
(for DB model look also here: nhibernate criteria for selecting from different tables)
What does this mean and how can I correctly retrieve my result list?
Thx
In Model.Order class, ID should be of type Model.Unit.
Make sure you have classes for both Model.Order and Model.Unit