стратегии выборки для коллекций абстрактного типа - PullRequest
0 голосов
/ 11 августа 2011

Итак, вот ситуация: предположим, у меня есть структура класса, используемая для представления гибкого поиска:

public class SearchDefinition
{
    public virtual string Name {get; set;}
    public virtual IEnumerable<SearchTerm> Terms {get; set;}
}

public abstract class SearchTerm
{
    public virtual Operator Op {get; set; } //i.e 'In', 'Not in', 'Contains' etc..
    public abstract IEnumerable<object> CompareValues {get; } //the values against which the search is performed. for example- 'in (2,6,4)', 'contains ('foo', 'blah')'.
}

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

public class NameSearchTerm : SearchTerm
{
   public virtual IEnumberable<string> ConcreteValues {get; set;}
   public override IEnumberable<object> CompareValues 
     {
        get
        {
            return ConcreteValues.Cast<object>();
        }
     }
}

и т. Д. С коллекциями разных типов.
Термины сопоставляются с использованием таблицы на иерархию, за исключением коллекций ConcreteValues, которые сопоставляются с различными таблицами (таблица для строковых значений, таблица для значений int и т.

мой вопрос - как мне эффективно получить список SearchDefinition с? для коллекции SearchTerm s я не могу использовать стратегию select (приведет к выбору N + 1).
Однако выборка с использованием JoinQueryOver или JoinAlias при отправке правильного запроса не заполняет коллекцию:

var definitions = session.QueryOver<SearchDefinition>()
   .Where(/*condition*/)
   .JoinAlias(d=> d.Terms, () => termsAlias)
   .List();   //sends a correct, joined query which fetches also the terms from the terms table

Assert.IsTrue(NHibernateUtil.IsInitialized(definitions[0].Terms)); //THIS FAILS!

какие-либо предложения о том, как это сделать?
Я добавляю текущие отображения здесь -

коллекция терминов внутри SearchDefinition класса:

 mapping.HasMany(x => x.Terms)
                //.Not.LazyLoad()
                .Fetch.Subselect()
                .Cascade.AllDeleteOrphan()
                .Cache.ReadWrite();

Коллекция конкретных значений внутри класса IntSearchTerm (аналогично для всех классов терминов):

mapping.HasMany<int>(t=> t.ConcreteValues).Table("TermsIntValues").Element("IntValue")
                //.Not.LazyLoad()
                .Fetch.Subselect()
                .Cascade.AllDeleteOrphan();

Ответы [ 2 ]

1 голос
/ 11 августа 2011

При использовании JoinQueryOver или JoinAlias NHibernate не будет инициализировать коллекцию, потому что вы можете / действительно отфильтровать Условия, поэтому могут быть не все выбранные Условия, которые есть в коллекции Условий. Я могу думать только о подзапросе.

var subquery = QueryOver.For<SearchDefinition>()
   .Where(/*conditions*/)
   .JoinAlias(d=> d.Terms, () => termsAlias)
   .Where(/*Terms.conditions*/)
   .Select(def => def.Id);

var definitions = session.QueryOver<SearchDefinition>()
   .WithSubquery.WhereProperty(def => def.Id).In(subquery);
   .List();

Assert.IsTrue(NHibernateUtil.IsInitialized(definitions[0].Terms));
0 голосов
/ 08 сентября 2011

Дело в том, что как только стратегия выборки в файле сопоставления определена как 'SubSelect', инициализация одного типа коллекции будет инициализировать его для всех объектов, которые содержат этот тип коллекции.
См. Следующий рабочий код для получения дополнительной информации.детали:

    var subQuery = QueryOver.Of<SearchDefinition>()
        .Where(p => p.IsActive)
        .Select(p => p.Id);

    var searchDefinitionsQuery = Session.QueryOver<SearchDefinition>()
        .WithSubquery.WhereProperty(p => p.Id).In(subQuery);
        searchDefinitionsQuery.OrderBy(p => p.SortOrder).Asc();

    var searchDefinitionsWithTerms = searchDefinitionsQuery.Future();

    var intValuesQuery = Session.QueryOver<IntValuesTerm>()
        .WithSubquery.WhereProperty(c => c.SearchDefinition.Id).In(subQuery)
        .Future();

    var stringValuesQuery = Session.QueryOver<StringValuesTerm>()
        .WithSubquery.WhereProperty(c => c.SearchDefinition.Id).In(subQuery)
        .Future();

    var timespanValuesQuery = Session.QueryOver<TimeSpanValuesTerm>()
        .WithSubquery.WhereProperty(c => c.SearchDefinition.Id).In(subQuery)
        .Future();


    if (searchDefinitionsWithTerms.Count() == 0)
    {
        return searchDefinitionsWithTerms;

    }

    /*if the searchDefinitions collection isn't empty- make sure all collections are initialized.
     * 
     * since our fetching strategies are all 'SubSelect' (see SearchDefinitionMappingOverride, SearchDefinitionTermsMappingOverride),
     * all we need to do is inialize ONE collection of each type (intValuesTerms, string values Terms etc..), and then automatically all other collections of the same type will also be initialized.
     * (look at the generated sql query for further info).
     * for example: if we have 5 searchDefinitions, each with 1 Term of type 'IntValuesTerm', it's enough to initialize just one of those collections, and then all others of the same type will be initialized as well.
     */


    //need to initalize each type of collection (int, string, timespan) once, in order for all the collections of that type to initialize
    IntValuesTerm intTerm = (IntValuesTerm) searchDefinitionsWithTerms.SelectMany(p => p.Terms).FirstOrDefault(c => c is IntValuesTerm);
    if (intTerm != null )
    {
        NHibernateUtil.Initialize(intTerm.IntValues);
    }

    StringValuesTerm stringTerm = (StringValuesTerm)searchDefinitionsWithTerms.SelectMany(p => p.Terms).FirstOrDefault(c => c is StringValuesTerm);
    if (stringTerm != null)
    {
        NHibernateUtil.Initialize(stringTerm.StringValues);
    }

    TimeSpanValuesTerm timespanTerm = (TimeSpanValuesTerm)searchDefinitionsWithTerms.SelectMany(p => p.Terms).FirstOrDefault(c => c is TimeSpanValuesTerm);
    if (timespanTerm != null)
    {
        NHibernateUtil.Initialize(timespanTerm.TimeSpanValues);
    }

    return searchDefinitionsWithTerms; 
...