获取抽象类型集合的策略

时间:2011-08-11 10:32:44

标签: nhibernate queryover

所以情况就是这样:假设我有一个用于表示灵活搜索的类结构:

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

等等,有不同类型的集合 使用table-per-heirarchy映射术语,但ConcreteValues集合除外,它们映射到不同的表(字符串值表,int值表等)。

我的问题是 - 如何有效地检索SearchDefinition的列表?对于SearchTerm的收集,我无法使用select策略(将导致选择N + 1)。
但是,在发送正确查询时使用JoinQueryOverJoinAlias进行提取不会填充集合:

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 个答案:

答案 0 :(得分:1)

使用JoinQueryOverJoinAlias时,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));

答案 1 :(得分:0)

问题是,一旦映射文件中的提取策略被定义为“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; 
相关问题