Состояние Nhibernate при соединении с IQueryOver - PullRequest
0 голосов
/ 08 декабря 2011

Я искал похожую проблему в google и stackoverflow в течение почти 2 часов, но не нашел никакого решения.

У меня есть 2 таблицы с отношением 1 ко многим.

1) [Accounts]
PK Account_Id
int User_ID


2) [Temporary_Accounts]
Fk Account_Id
char IsAccepted   {'1','0',null}
varchar name

И2 сопоставленных класса

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;

Я хочу отобразить все учетные записи для данного user_id с дополнительной информацией (например, имя) для учетных записей, у которых есть запись в [Temporary_Accounts] и IsAccepted == null.

, поэтомуSQL должен выглядеть так:

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);

, но мой IQueryOverquery:

IQueryOver<Acc> query = (...)
query.JoinAlias(f => f.Temp,
                () => Temp,
                JoinType.LeftOuterJoin)
     .Where(f => f.Temp.IsAccepted == null)
     .And(f => f.user.id == userid);

генерирует такой 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);

, поэтому я получаю меньше результатов, чем в первыйправильный запрос.

У вас есть идея, что мне следует изменить или что я могу сделать, чтобы получить результаты первого запроса?Моя идея состояла в том, чтобы покинуть таблицу учетных записей с помощью подзапроса, который выбирает все учетные записи IsAccepted = null из таблицы Temporary_Accounts, но я не уверен, как это сделать в Iqueryover или Icriteria.

Буду благодарен за любые советы

1 Ответ

6 голосов
/ 08 декабря 2011

Поскольку у вас есть 1-Many между Acc и Temp, ваш образец sql будет производить декартово произведение.

Запрос, который вам понадобится, использует подзапрос и выглядит примерно так:

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);

Создание SQL следующим образом:

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
)

Эта статья на nhibernate.info очень полезна для выяснения сложных запросов с помощью QueryOver.

UPDATE:

Если вам также нужно найти Accounts, у которого нет соответствующих строк в Temporary_Accounts, тогда вам нужно два подзапроса и дизъюнкция .

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))     
  );

ОБНОВЛЕНИЕ 2:

Начиная с NH 3.2, вы можете добавлять дополнительные условия в JOIN. Дополнительную информацию см. В этом ответе: Добавление условий к внешним соединениям с помощью запроса NHibernate ICriteria / QueryOver

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>();
...