Джойн с произвольным условием. Запрос к БД, принимающий фильтр в качестве параметра
Есть два солюшена: в одном .Net Framework и NHibernate, во втором .Net Core 3 и EF Core.
Есть задача: написать запрос с Join'ом к БД, который будет принимать фильтр в качестве параметра. Джойнятся сущности одной таблицы по свойству, которое просто числовое, никаких внешних ключей или связей один к одному. (По сути у нас есть черновая и актуальная версии сущности и в этом свойство хранится Id'шник актуальной, чтобы была возможность связать их).
Во втором проекте (с EF Core) сработал следующий LINQ запрос:
public async Task ApproveByPredicateAsync(Expression<Func<DraftAndApproved<TEntity>, bool>> predicate)
{
var draftsForApprove = await Query<T>() //IQueryable<T>
.Join(Query<T>(),
draft => draft.EntityId,
approved => approved.EntityId,
(draft, approved) => new DraftAndApproved<TEntity> { Draft = draft, Approved = approved })
.Where(predicate)
.Select(da => da)
.ToArrayAsync();
//other actions...
}
//somewhere above
public struct DraftAndApproved<TEntity>
{
public TEntity Draft { get; set; }
public TEntity Approved { get; set; }
public void Deconstruct(out TEntity draft, out TEntity approved)
{
draft = Draft;
approved = Approved;
}
}
Но NHibernate ругается на NotSupported для подобного Join'а (а ещё до этого для него структуру пришлось переделать в класс, ибо иначе он не видел пустой конструктор). С GroupJoin аналогичная история. Провал маппить в анонимный класс, а потом Select'ить перед Where, но тоже не сработало.
Пытался понять как такое написать на QueryOver, но там нет адекватного решения для Join'а не связанных друг с другом сущностей.
В варианте с Expression я мапплю в явный класс, чтобы можно было сигнатуру для входящего аргумента задать, иначе не понятно что просить на вход в Where.
Думал переписать запрос на DetachedCriteria, но опять же не могу понять как с её помощью написать джойн для не связанных сущностей: CreateCriteria требует от меня path, которого у меня нет.
Пытался написать CreateSqlQuery, но не понял как смаппить результат.
Session.CreateSQLQuery($@"
SELECT draft.*, approved.*
FROM {typeof(T).Name} draft
LEFT JOIN {typeof(T).Name} approved
ON draft.EntityState = 3
AND approved.EntityState = 1
AND draft.EntityId = approved.EntityId
AND draft.Version != approved.Version
WHERE {filter}
")
Всё упирается в то, что, как мне показалось, NHibernate не умеет джойнить не связанные сущности или даже хоть как-то работать с подобными запросами.
Попробовал такой вариант, но он падает в NRE на Select'ах. Видимо не проджектится адекватно.
T draftAl = null;
T approvedAl = null;
DraftAndApproved<T> dto = null;
var draftsForApproveObj = Session.QueryOver(null, () => draftAl)
.JoinEntityAlias(() => approvedAl,
() => approvedAl.EntityId == draftAl.EntityId,
JoinType.LeftOuterJoin)
.Where(filter)
.SelectList(list => list
.Select(x => draftAl).WithAlias(() => dto.Draft)
.Select(x => approvedAl).WithAlias(() => dto.Approved))
.TransformUsing(Transformers.AliasToBean<DraftAndApproved<T>>())
.List<DraftAndApproved<T>>();
System.NullReferenceException: Object reference not set to an instance of an object.
at NHibernate.Criterion.ConstantProjection..ctor(Object value)
at NHibernate.Criterion.Projections.Constant(Object obj)
at NHibernate.Impl.ExpressionProcessor.FindMemberProjection(Expression expression)
at NHibernate.Criterion.Lambda.QueryOverProjectionBuilder`1.Select(Expression`1 expression)
at Granit.Cssp.Controllers.Productions.ProductionMigrationController.<>c__DisplayClass13_0`1.<ApproveItemsByPredicate>b__3(QueryOverProjectionBuilder`1 list)
at NHibernate.Criterion.QueryOver`2.NHibernate.IQueryOver<TRoot,TSubType>.SelectList(Func`2 list)
at Granit.Cssp.Controllers.Productions.ProductionMigrationController.ApproveItemsByPredicate[T](DataScope ds, Expression`1 filter)