I was searching for similar problem on google and stackoverflow for almost 2 hours but did not find any solution.
I have 2 tables with relation 1 to many.
1) [Accounts]
PK Account_Id
int User_ID
2) [Temporary_Accounts]
Fk Account_Id
char IsAccepted {'1','0',null}
varchar name
And 2 mapped classes
1) Acc
int Id;
User user;
TempAcc Temp; //cause each Account can have 0 or one TempAcc (with IsAccepted == null)
2)TempAcc
int Id;
bool IsAccepted;
string name;
I want to display all accounts for given user_id with additional information(f.e name) for Accounts which has record in [Temporary_Accounts] and IsAccepted == null.
so the SQL should look like:
select acc.Account_Id, acc.User_Id, tempacc.Name
from Account acc left join Temporary_Account tempacc
on (acc.Account_ID = tempacc.Account_Id and tempacc.IsAccepted is null)
where (acc.User_Id = 65);
but my IQueryOverquery:
IQueryOver<Acc> query = (...)
query.JoinAlias(f => f.Temp,
() => Temp,
JoinType.LeftOuterJoin)
.Where(f => f.Temp.IsAccepted == null)
.And(f => f.user.id == userid);
generates such sql:
select acc.Account_Id, acc.User_Id, tempacc.Name
from Accounts acc left join Temporary_Accounts tempacc
on (acc.Account_ID = tempacc.Account_Id)
where (acc.User_Id = 65 and tempacc.IsAccepted is null);
so I am getting less results than in first correct query.
Do you have any Idea what should I change or what could I do to obtain results from first query ? My Idea was to leftjoin Accounts table with subquery which selects all IsAccepted=null accounts from Temporary_Accounts table ,but I am not sure how to do it in Iqueryover or Icriteria.
I will be grateful for any advices
Since you have a 1-Many between Acc
and Temp
your sample sql will produce a Cartesian product.
The Queryover you will need uses a Subquery and looks something like the following:
Acc accountAlias = null;
var subQuery = QueryOver.Of<Temp>()
.Where(x=>x.IsAccepted==null)
.And(x=>x.Account.Id==accountAlias.Id);
var results = session.QueryOver<Acc>(()=>accountAlias)
.Where(x=>x.User.Id==65)
.WithSubquery.WhereExists(subQuery);
Producing SQL like this:
select *
from Accounts a
where a.User_Id=65
and exists (
select t.Account_Id
from Temporary_Accounts t
where t.IsAccepted is null and t.Account_Id=a.Account_Id
)
This article on nhibernate.info is very helpful for figuring out complex queries with QueryOver.
UPDATE:
If you need to also find Accounts
which do not have any corresponding rows in Temporary_Accounts
then you need two subqueries and a Disjunction.
Acc accountAlias = null;
var hasTempAccount = QueryOver.Of<Temp>()
.Where(x=>x.IsAccepted==null)
.And(x=>x.Account.Id==accountAlias.Id);
var doesNotHaveTempAccount = QueryOver.Of<Temp>()
.And(x=>x.Account.Id==accountAlias.Id);
var results = session.QueryOver<Acc>(()=>accountAlias)
.Where(x=>x.User.Id==65)
.Where(Restrictions.Disjunction()
.Add(Subqueries.WhereExists(hasTempAccount))
.Add(Subqueries.WhereNotExists(doesNotHaveTempAccount))
);
UPDATE 2:
Since NH 3.2 you can add extra conditions to a JOIN. See this answer for further details: Adding conditions to outer joins with NHibernate ICriteria/QueryOver query
Temp tempAlias = null;
Account accountAlias = null;
dto dto = null;
var results = Session.QueryOver<Account>(()=>accountAlias)
.JoinAlias(x=>x.TempAccounts,()=>tempAlias,JoinType.LeftOuterJoin,
Restrictions.IsNull(Projections.Property(()=>tempAlias.IsAccepted))
)
.Where(x=>x.Account.Id==65)
.SelectList(list=>list
.Select(()=>accountAlias.Id).WithAlias(()=>dto.AccountId)
.Select(()=>accountAlias.User.Id).WithAlias(()=>dto.UserId)
.Select(()=>tempAlias.Name).WithAlias(()=>dto.TempAccName)
)
.SetResultTransformer(Transformers.AliasToBean<dto>())
.List<dto>();