2015-12-17 5 views
0

У меня есть интерфейс:параметров универсального типа для ковариантного Params массива

IReadOnlyList<TEntity> Execute<TEntity>(ILambdaQuery<TEntity> query, params ILambdaQuery<TAssociatedEntity>[] associations) where TAssociatedEntity : class, IAggregateRoot; 

Интерфейс используется для выполнения запроса в хранилище. Где TEntity - тип, который вы ожидаете назад, и то, что вы запрашиваете, и TAssociatedEntity - это другие запросы, которые вы хотите запустить, и которые будут объединены с результатами TEntity, которые возвращаются. Например.

var courseQuery = LambdaQuery<Course>(c => c.Id == id); 

var studentsQuery = LambdaQuery<Student>(s => s.Courses.Any(c => c.id == id)); 

var instructorsQuery = LambdaQuery<Instructor>(i => i.Courses.Any(c => c.id == id)); 

var courses = this.repo.Execute<Course>(courseQuery, studentsQuery, instructorsQuery); 

мой вопрос вращается вокруг этого параметра:

params ILambdaQuery<TAssociatedEntity>[] associations 

Таким образом, вы можете видеть из примера, массив ассоциации могут иметь различные типы TAssociatedEntity. Итак, как мне сказать компилятору?

Интерфейс выше не выполняет эту работу. Компилятор говорит, что тип имени пространства имен "TAssociatedEntity" не найден ...

Возможно ли это? Есть идеи?

EDIT - запрос для получения дополнительной информации:

В LambdaQuery магазины класса а, где выборка пункт лямбда-выражения и ассоциированным с лица нетерпеливым. Множество из них передаются в хранилище Execute метод, который делает:

public IReadOnlyList<TEntity> Execute(ILinqQuery<TEntity> query, params ILinqQuery<TAssociatedEntity>[] 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 (ILinqQuery<TEntity> association in associations) 
     { 
      var queryOverAssociation = this.GetSession().QueryOver<TEntity>().Where(association.WhereClause); 

      association.FetchExpressions.ToList().ForEach(expression => queryOverAssociation = queryOverAssociation.Fetch(expression).Eager); 

      queryOverAssociation.Future<TEntity>(); 
     } 

     return this.ExecuteInTransaction(() => future.ToList()); 
    } 

Он выполняет запрос и любые связанные с ними вопросы, как партии и возвращает запрошенную TEntity с любыми биты заполняются

EDIT -. больше информации

Я не блестящий с nHibernate. Но в моих простых терминах все, что выполняется с Future(), задерживается до вызова ToList().

Так что, если у меня был:

class Person 
{ 
    public string Id { get; set; } 
    public List<Arm> Arms { get; set; } 
    public List<Leg> Legs { get; set; } 
} 

class Arm 
{ 
    public string Id { get; set; } 
    public List<Hand> Hands { get; set; } 
} 

class Leg 
{ 
    public string Id { get; set; } 
    public List<Foot> Feet { get; set; } 
} 

Таким образом, если метод Execute был вызван для TEntity людей, и человек имел коллекцию оружия, каждый из которых имеет руку и коллекцию ног, каждые из которых имеет ножку, то мы могли бы иметь вызываемые Казнить, который выглядит как:

LambdaQuery<Person> personQuery = new LambdaQuery<Person>(t => t.Id == "123"); 
LambdaQuery<Arm> armQuery = new LambdaQuery<Arm>(t => t.Person.Id == "123", t => t.Hands); 

Person person = personRepository.Execute(personQuery, armQuery); 

Тогда бы отправить следующую базу данных в пакете:

SELECT * FROM person WHERE personId = '123'; 
SELECT * FROM arms a JOIN hands h ON h.armId = a.armId WHERE a.personId = "123" 

А (безногий) объект Person будет возвращен из метода Execute с:

Person 
------ 
{ 
    Id : "123", 
    Arms : [ { Id : "ARM1", Hands : [ { Id : "HAND1" }, { Id : "HAND2" } ] } ], 
    Legs: null 
} 
+0

Я подумал об этом немного, и я думаю, нам нужно знать немного больше о том, что вы пытаетесь сделать. В частности, что конкретно находится в интерфейсе «ILambdaQuery» и как вы собираетесь использовать экземпляры «ILambdaQuery» в вашем массиве 'params' внутри функции« Execute »? – Medo42

+0

@ Medo42 Спасибо за ваш вопрос. Я обновил сообщение. Это дает вам необходимую информацию? – Adam

+0

Можете ли вы объяснить, как ассоциации влияют на результат Execute? Кажется, что ваш код запускает запросы для них, которые в конечном итоге нигде. Вы хотите вернуть все основные и ассоциированные объекты вместе в одну структуру данных в конце? Или ассоциации просто ограничивают набор основных объектов, которые должны быть возвращены? В любом случае, ваш пример кода, похоже, не делает ни того, ни другого. – Medo42

ответ

1

Извините за долгое ожидание, вот что я могу вам рассказать.

Преамбула

Одна из причин, почему у меня есть проблема с ответом является то, что части установки вопроса на самом деле не кажется, подходят друг к другу, так что вещи не могли действительно работать так, как вы (или я) представить. И действительно, метод 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()); 
} 

Недостатком здесь является то, что вы могли бы пройти ничего в этой ассоциации массива в настоящее время, и было бы только не во время выполнения.

+0

Спасибо @ Medo42. Это работает для меня, я бы предпочел не перекладывать код на класс Lambda Query, но я не могу думать о другом. Информация о магии фьючерсов nHibernate -> https://ayende.com/blog/3979/nhibernate-futures – Adam

+0

Вы можете вытащить фактический код из LambdaQuery, используя шаблон посетителя. Я могу добавить пример для этого позже. – Medo42

+0

Обновленный ответ с дополнительной информацией о том, как отделить LambdaQuery от его использования. – Medo42

0

Вы Havent объявили тип TAssociatedEntity как универсального типа, это можно сделать двумя способами.

Либо изменить метод (который, вероятно, что вы хотите сделать)

IReadOnlyList<TEntity> Execute<TEntity,TAssociatedEntity>... 

или интерфейс, если TAssociatedEntity должен быть ковариантны.

public interface IDontKnow<in TAssociatedEntity> 
+0

Спасибо @CSharpie. Я слышал о ковариации раньше и понимаю, что это такое, но никогда не сталкивался с этим. В этом случае мой интерфейс будет изменен на открытый интерфейс IQueryableRepository . Это имеет смысл для меня, но это дает мне ошибку Invalid variance и говорит, что TAssociatedEntity должно быть инвариантным. Я не могу смешивать TEntity и TAssociatedEntity? – Adam

+0

Нет, вы можете использовать только 'in'modifier, если этот тип используется только как параметр в интерфейсе. Вероятно, у вас есть другой метод, возвращающий тип, поэтому вам не разрешено использовать его. Возможно, вы захотите прочитать [некоторые вещи] (http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx), чтобы получить эту идею. или просто опустить 'in' пока. – CSharpie

+0

Спасибо @CSharpie. К сожалению, мне кажется, что мне нужно ключевое слово. Мне нужна смесь TAssociatedEntitys в этой коллекции. Я получу чтение ... – Adam

Смежные вопросы