问题
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 SearchDefinition
s? for the collection of SearchTerm
s 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