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,则很容易进行。

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

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

下面的方法需要一些改进,但无论如何都是这样。我希望它对某人有用。我可能稍后回来把它清理干净。

对于LINQ、HQL和SQL查询期货,请使用:

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

不过要小心。当以后没有该类型的查询时调用此方法将导致抛出异常。

我认为最好的"解决方案"是根本不在视图模型类中使用IEnumerable——为什么不使用简单的数组呢?这将迫使你在控制器中完全填充你的模型,你不会失去Linq功能等。

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

所以SearchFormData应该是这样的:

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

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

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

也可以使用GetFutureValue来代替GetEnumerator
如果不能使用await,可以使用task。结果进行阻塞等待,或者您可以使用GetEnumerableAsync/GetValueAsync的现有同步版本。

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

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

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

最新更新