我(并且应该)强制NHibernate将来的查询在某一点执行吗?

时间:2011-06-13 16:55:30

标签: asp.net-mvc nhibernate

我在MVC 3 Web应用程序中使用NHibernate未来查询,并试图在我的控制器中保留所有数据库访问权限,而不是在我的视图中。该站点是资源(描述性模糊)的目录,其具有多对多的成绩集合以及多对多的主题集合。用户选择一个或多个成绩,以及一个或多个主题,然后获取匹配资源列表。

为了填充搜索表单,我正在使用将来的查询来获取所有成绩和主题:

    public Domain.SearchFormData GetSearchFormData()
    {
        // Get all grades and all topics using a multiquery.

        IEnumerable<Grade> grades = Session.QueryOver<Grade>()
            .Future();

        IEnumerable<Topic> topics = Session.QueryOver<Topic>()
            .Future();

        var result = new SearchFormData();
        result.Grades = grades;
        result.Topics = topics;
        return result;
    }

这非常简单且工作正常,并返回SearchFormData,这是一个简单的DTO。此查询在控制器中执行,结果不需要任何进一步处理,因此它会直接传递到视图中以呈现为复选框列表。

但是由于未来查询的延迟执行,在视图开始迭代列表之前不会触发数据库访问。一些人(如NHibernate Profiler)认为这是一个禁忌,他们说,在视图开始渲染时,应该完成所有数据库访问。主要原因是,如果不这样做,很容易隐藏SELECT N + 1。

这不适用;等级和主题都是没有集合的简单对象。我不介意该视图将触发数据库访问。但它让我寻找一种干净,清晰的方式来触发所有未来的查询。一种方法是访问结果,例如将IEnumerable复制到列表中。但这不是我需要做的事情;我只想执行排队查询。

我认为触发器在上面的查询之外发生是有意义的。我可能需要其他数据来呈现整个页面视图,我可能正在使用其他未来的查询来获取它。例如,主页还可能显示五个最受欢迎的资源和资源总数。然后控制器将调用多个方法来累积视图所需的内容,并且最有效的是一次执行所有这些方法。当然,一种方法是将我的GetSearchFormData扩展为GetAllHomePageData并返回一个DTO,其中包含主页上所有内容的字段。但我使用搜索表单,而不仅仅是在主页上。所以我会以这种方式失去一些很好的模块化。

3 个答案:

答案 0 :(得分:2)

以下方法需要一些改进,但无论如何它都在这里。我希望它对某人有用。我可以稍后回来清理它。

对于LINQ,HQL和SQL Query期货,请使用:

public static void ExecuteFutureQueries(this ISession session)
{
    var sessionImpl = (ISessionImplementor) session;
    var dummy = sessionImpl.FutureQueryBatch.Results;
}

对于QueryOver和ICriteria期货,请使用:

public static void ExecuteFutureCriteria(this ISession session)
{
    var sessionImpl = (ISessionImplementor) session;
    var dummy = sessionImpl.FutureCriteriaBatch.Results;
}
但是,要小心。

表示在将来没有该类型的查询时调用此方法将导致异常抛出。

答案 1 :(得分:1)

我认为对此最好的“解决方案”不是在视图模型类中使用IEnumerable - 为什么不使用简单的数组?这将迫使您在控制器中完全填充模型,并且不会丢失Linq功能等。

第二个问题是您直接在SearchFormData中使用域实体(成绩,主题) - 您应该使用DTO。你提到SearchFormData是DTO,但这并不完全正确,因为它引用了你的域实体。

所以SearchFormData看起来应该是这样的:

public class SearchFormData
{
  public GradeDTO[] Grades { get; set; }
  public TopicDTO[] Topics { get; set; }
}

你真正的问题是与设计相关的问题,而不是NHibernate Futures问题IMO。

答案 2 :(得分:1)

Daniel Schilling 的答案很好,但自那时起NH的API已经发生了一些变化。未来的批处理程序将不再提供属性.Results

对于NHibernate 5.0,它看起来应该是这样的:

var eng = (NHibernate.Engine.ISessionImplementor)sess;
await eng.FutureCriteriaBatch.GetEnumerator<object>().GetEnumerableAsync();
await eng.FutureQueryBatch.GetEnumerator<object>().GetEnumerableAsync();

也可以使用GetEnumerator代替GetFutureValue 如果您无法使用await,则可以使用task.Result执行阻止等待,也可以使用GetEnumerableAsync / GetValueAsync的现有同步版本。

此外,丹尼尔的警告仍然适用:当你尝试这样做时,FutureCriteriaBatch和FutureQueryBatch都会抛出它们。它们是空的。

不幸的是在NH5.0中无法检查它们是否为空,所以唯一的选择是:

  • 的try-catch-忽略
  • 使用反射检查FutureCriteriaBatch.queries.Count
  • 添加虚拟未来查询以确保至少有一个