Извините за долгое ожидание, вот что я могу вам рассказать.
Преамбула
Одна из причин, почему у меня есть проблема с ответом является то, что части установки вопроса на самом деле не кажется, подходят друг к другу, так что вещи не могли действительно работать так, как вы (или я) представить. И действительно, метод Execute
, который вы показываете, на самом деле не выглядит do что-либо с associations
, которые переданы, по крайней мере, ничего, что могло бы обеспечить какой-либо наблюдаемый результат, насколько я интерпретирую вещи.
Я никогда не пользовался NHibernate, но я использую Hibernate, и я ожидаю, что вам действительно не нужно писать запрос для получения связанных объектов - пока вы правильно настроили ассоциации, ORM должен иметь возможность позаботьтесь об этом для вас и либо с нетерпением загружайте связанные объекты, когда вы запрашиваете основной объект, либо лениво загружаете их при доступе к ним.
Но даже если вам нужно запросить связанные сущности вручную, я ожидаю, что вам нужно будет каким-либо образом связать их с вашими основными объектами, если вы не хотите, чтобы ваш метод Execute
возвращал отдельные списки основных объектов и каждого связанного объекта (но тогда вы можете также избавиться от параметра associations
и просто запросить для каждого списка отдельно). И я ничего не вижу в вашем коде Execute
, который связывает результаты запросов ассоциации с основными сущностями (или даже возвращает результаты этих запросов).
К сожалению, для вашего фактического вопроса о типах подписи важно, как именно вы планируете использовать объекты запроса, и что вы можете с ними делать, и это все еще не совсем ясно для меня.
Однако вы все равно можете понять, что вам нужно от любой попытки ответить, поэтому я просто выберу одну интерпретацию того, как все это должно работать и работать от этого.
Давайте добраться до точки
Из вашего вопроса, Execute
займет один запрос, чтобы найти список «главных» лиц на возвращение, а также дополнительные вопросы, которые используются, чтобы найти связанные с ним объекты, которые будут возвращены как часть объектов основного объекта. Я буду интерпретировать вашу реализацию Execute
как незавершенную попытку выполнить эту работу. Я также предполагаю, что списки связанных объектов, которые вы можете получить через запросы associations
, содержат достаточно информации, чтобы выяснить, какой связанный объект соединяется с какой основной сущностью, даже если это кажется мне далеким от тривиального.
Так короче говоря, associations
запросы будут только можно использовать, чтобы сделать несколько запросов для списков связанных с ними объектов, а также каким-то магическим способом (критически не с участием объектов запроса) сущности в этих списках будет связанных с правильными основными объектами.
Ваш вопрос состоит в том, как вы можете передать несколько associations
запросов в Execute
, что может быть запросом для разных типов сущностей и, следовательно, иметь разные общие параметры типа.
Прежде всего, списки и массивы могут содержать только объекты фиксированного типа.Вы можете поместить String и Timer в один и тот же массив, но только если тип элемента для массива является общим суперклассом для обоих - в этом случае будет работать только массив из object
.
Для вашего параметра associations
, вы пытаетесь пройти как LambdaQuery<Student>
и LambdaQuery<Instructor>
в том же массиве, поэтому тип элемента массива в должны быть супертипом обоих. object
всегда работает, но не очень полезно:
IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params object[] associations); // now what?
Вы можете передать в запросах таким образом, но вы не будете иметь много удовольствия, пытаясь использовать их. Что не работает, очень важно, это:
IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params LambdaQuery<object>[] associations);
Для того, чтобы работать, LambdaQuery<T>
должен быть ковариантны, но это вступает в противоречие с тем, что вы можете получить прямой доступ, где положение в объекте запроса, который должен был бы быть contra вариант во всяком случае. Нет, решение здесь не связано с общей дисперсией.
Если у вас есть контроль над иерархией LambdaQuery<T>
класса, один из решений может быть, чтобы сделать еще один класс LambdaQuery
(без параметра типа!), Который является производным от LambdaQuery<T>
. Тогда вы могли бы перейти в список LambdaQuery
:
IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params LambdaQuery[] associations); // Yes!
LambdaQuery
будет общим для всех супертипом LambdaQuery<T>
, так что теперь будет компилировать! Однако как вы с ним работаете? Это где вещи становятся немного Hairy, так как я не знаю, как вы могли бы соединить объекты вместе, но представьте себе, что-то вроде этого:
public abstract class LambdaQuery
{
public abstract void DoAssociationStuff<TMainEntity>(TMainEntity mainEntity, Session session);
}
public abstract class LambdaQuery<TEntity> : LambdaQuery
{
public void DoAssociationStuff(Session session)
{
var queryOverAssociation = session.QueryOver<TEntity>().Where(this.WhereClause);
this.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
queryOverAssociation.Future<TEntity>();
}
}
public IReadOnlyList<TEntity> Execute(LambdaQuery<TEntity> query, params LambdaQuery[] associations)
{
var queryOver = this.GetSession().QueryOver<TEntity>().Where(query.WhereClause);
query.FetchExpressions.ToList().ForEach(expression => queryOver = queryOver.Fetch(expression).Eager);
var future = queryOver.Future<TEntity>();
foreach (LambdaQuery association in associations)
{
association.DoAssociationStuff(this.GetSession());
}
return this.ExecuteInTransaction(() => future.ToList());
}
Идея заключается в том, чтобы иметь список вещей некоторого общего типа, не выставляет общий параметр типа, но может передать выполнение методу подкласса, который знает о параметре общего типа.
Я надеюсь, что это поможет вам или, по крайней мере, дает некоторое вдохновение для поиска решения.
Bonus: Seperate относится с рисунком для посетителей
Вы упомянули вы не хотите Execute
-специфический код, чтобы быть частью иерархии LamdaQuery
класса. Это имеет большой смысл, потому что на самом деле не стоит беспокоиться о том, как он используется, и вы не хотите его менять или добавлять к нему для следующего случая использования, который приходит.
К счастью, есть решение, но, как всегда, с этими вещами требуется немного косвенности. Идея заключается в том, чтобы реализовать шаблон посетителя для LambdaQuery:
public interface LambdaQueryVisitor
{
void Visit<TEntity>(LambdaQuery<TEntity> visited);
}
public abstract class LambdaQuery
{
public abstract void Accept(LambdaQueryVisitor visitor);
}
public class LambdaQuery<TEntity> : LambdaQuery
{
public override void Accept(LambdaQueryVisitor visitor)
{
visitor.Visit(this);
}
}
Это весь код, нужно добавить в LambdaQuery<T>
и его суперкласс LambdaQuery
, и она не связана со спецификой того, что вы хотите сделать.Но она позволяет работать с коллекциями LambdaQueries в любом контексте, путем реализации посетителя:
public AssociationQueryVisitor : LambdaQueryVisitor
{
private readonly Session m_session;
public AssociationQueryVisitor(Session session)
{
m_session = session;
}
public void Visit<TEntity>(LambdaQuery<TEntity> query)
{
var queryOverAssociation = m_session.QueryOver<TEntity>().Where(query.WhereClause);
query.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
queryOverAssociation.Future<TEntity>();
}
}
public IReadOnlyList<TEntity> Execute(LambdaQuery<TEntity> query, params LambdaQuery[] associations)
{
var queryOver = this.GetSession().QueryOver<TEntity>().Where(query.WhereClause);
query.FetchExpressions.ToList().ForEach(expression => queryOver = queryOver.Fetch(expression).Eager);
var future = queryOver.Future<TEntity>();
AssociationQueryVisitor visitor = new AssociationQueryVisitor(this.GetSession());
foreach (LambdaQuery association in associations)
{
association.Accept(visitor);
}
return this.ExecuteInTransaction(() => future.ToList());
}
Bonus Bonus: динамический
Вы должны быть в состоянии получить тот же результат с меньшим количеством кода и без любое изменение на LambdaQuery<T>
вообще с помощью динамического набора. Динамическая типизация - очень мощный инструмент, но мнения сильно отличаются от того, как и когда это целесообразно использовать. Вот как это будет выглядеть (я надеюсь, что это все из памяти и не проверяется;.))
public void DoAssociationStuff<TEntity>(Session session, LambdaQuery<TEntity> query)
{
var queryOverAssociation = session.QueryOver<TEntity>().Where(query.WhereClause);
query.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager);
queryOverAssociation.Future<TEntity>();
}
public IReadOnlyList<TEntity> Execute(LambdaQuery<TEntity> query, params dynamic[] associations)
{
var queryOver = this.GetSession().QueryOver<TEntity>().Where(query.WhereClause);
query.FetchExpressions.ToList().ForEach(expression => queryOver = queryOver.Fetch(expression).Eager);
var future = queryOver.Future<TEntity>();
foreach (dynamic association in associations)
{
DoAssociationStuff(this.GetSession(), association);
}
return this.ExecuteInTransaction(() => future.ToList());
}
Недостатком здесь является то, что вы могли бы пройти ничего в этой ассоциации массива в настоящее время, и было бы только не во время выполнения.
Я подумал об этом немного, и я думаю, нам нужно знать немного больше о том, что вы пытаетесь сделать. В частности, что конкретно находится в интерфейсе «ILambdaQuery» и как вы собираетесь использовать экземпляры «ILambdaQuery» в вашем массиве 'params' внутри функции« Execute »? – Medo42
@ Medo42 Спасибо за ваш вопрос. Я обновил сообщение. Это дает вам необходимую информацию? – Adam
Можете ли вы объяснить, как ассоциации влияют на результат Execute? Кажется, что ваш код запускает запросы для них, которые в конечном итоге нигде. Вы хотите вернуть все основные и ассоциированные объекты вместе в одну структуру данных в конце? Или ассоциации просто ограничивают набор основных объектов, которые должны быть возвращены? В любом случае, ваш пример кода, похоже, не делает ни того, ни другого. – Medo42