fetching strategies for collections of abstract type

爷,独闯天下 提交于 2019-12-20 06:28:08

问题


so here's the situation: suppose I have a class structure used to represent flexible search:

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')'.
}

now, since search terms can refer to different fields, each type of term has its own class:

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

and so on, with collections of different types.
Terms are mapped using table-per-heirarchy, except for the ConcreteValues collections, which are mapped to different tables (a table for string values, a table for int values etc..).

my question is- how do I efficiently retrieve a list of SearchDefinitions? for the collection of SearchTerms I can't use select strategy (will result in select N+1).
However, fetching using JoinQueryOver or JoinAlias, while sending the correct query, does not populate the collection:

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!

any suggestions on how to do this?
I'm adding the fluent mappings here-

the terms collection inside the SearchDefinition class:

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

the Concrete values collection inside the IntSearchTerm class (similar for all term classes):

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

回答1:


When using JoinQueryOver or JoinAlias NHibernate won't initialize the collection because you could/do filter out Terms so there might be not all Terms fetched which are in the Terms collection. The only way i can think of is a subquery.

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



回答2:


the thing is that once the fetching strategy in the mapping file is defined as 'SubSelect', initializing one type of collection would initialize it on all objects that contain that type of collection.
See the follwing working code for further details:

    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; 


来源:https://stackoverflow.com/questions/7024348/fetching-strategies-for-collections-of-abstract-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!