使用Linq-to-Nhibernate从50多个表中获取数据

时间:2016-03-17 19:19:00

标签: c# sql-server linq nhibernate

我有一个Web服务,触及50多个数据库表(数据库严格规范化),以便创建响应。该服务返回在客户指定的日期范围内修改的所有航程。

出于性能原因,我想避免延迟加载,在映射到响应类型之前尽可能多地获取图形。

我已将查询分解为较小的部分,使用Nhibernate Fetch + ToFuture急切加载我需要的数据:

var fetchQuery = Session.Query<Voyage>()
.Fetch(v => v.VoyageStatus)
.FetchMany(v => v.VoyageLocations)
.Where(v => voyageIds.Contains(v.VoyageID))
.ToFuture();

Session.Query<Ship>()
.FetchMany(s => s.ShipCsos)
.Where(s => shipIds.Contains(s.ShipID))
.ToFuture();

Session.Query<Ship>()
.Fetch(s => s.ShipFlagCode)
.ThenFetch(sf => sf.Country)
.Fetch(s => s.ShipType)
.Fetch(s => s.ShipStatus)
.Fetch(s => s.ShipSource)
.Fetch(s => s.ShipHullType)
.Fetch(s => s.ShipLengthType)
.Fetch(s => s.ShipBreadthType)
.Fetch(s => s.ShipSpeedType)
.Fetch(s => s.ShipPowerType)
.FetchMany(s => s.ShipAttributes)
.ThenFetch(sa => sa.ShipAttributeName)
.Where(s => shipIds.Contains(s.ShipID))
.ToFuture();

//[Lots of similar Session.Query<X>...ToFuture() calls]

return fetchQuery.ToList();

问题

当日期范围达到特定范围时,我开始达到2100的SQL Server参数限制。我认为参数限制仅适用于单个IN子句,但它显然适用于整个查询;使用Futures我最终得到一个SQL查询,每个ToFuture调用都有一个SELECT语句(每个SELECT语句包含一个中等大小的IN子句)。

有解决方法吗?例如,有没有办法发送较小的期货组以保持参数限制并仍然保持实体水合?

我试过在期货中途进行fetchQuery.ToList()调用。这样可以防止参数限制异常,但根据Nhibernate Profiler(属性是延迟加载的),实体没有正确地保持水分。

任何指针都会非常感激!

1 个答案:

答案 0 :(得分:5)

实际上,你可以通过NHibernate为性能原因更好地保持延迟加载,即使在你的情况下也是如此。

(由于性能原因,想要切换到急切加载可能是不知道如何使用NHibernate优化延迟加载的标志.NHibernate可以避免延迟加载的经典n + 1性能问题。)

为什么延迟加载可以很好地与NH

一起使用

(即使在你的情况下。)

使用NHibernate进行延迟加载可能会非常出色。它倾向于在运行时性能和开发性能之间保持良好的平衡。高效执行,高效开发和维护。

调整实体和集合映射上的延迟加载batch-size属性。

(链接reference详细解释了它的工作原理。)

<class name="YourEntity" batch-size="20">
    ...
    <set name="SomeChildren" batch-size="15" ...>

配置导致NHibernate不仅在访问相关实体/集合时加载它们,而且还包括加载到它在会话第一级缓存中跟踪的batch-size - 1相关实体/集合。当然,请调整batch-size值以匹配您加载基数的常规情况。

这是一个非常强大的机制。它导致后续延迟加载调用的大部分内容已经存在,可以在没有额外往返DB的情况下使用。

(仅在会话使用不当的一些极端情况下,导致它引用许多与您当前工作无关的实体并且具有挂起的延迟加载,延迟加载批处理可能会严重失败。这种情况发生在这样的情况下情况,它可能会初始化太多与您的工作无关的待处理延迟加载。)

您可以使用全局配置参数default_batch_fetch_size为所有延迟加载的集合和实体全局配置默认批量大小(放在hibernate.cfg.xml文件中,或通过Configuration.SetProperty(Environment.DefaultBatchFetchSize, ...)设置)。

为什么急切加载可能是最糟糕的选择

相比之下,急切加载可以迅速产生“膨胀代码”,并为每种情况进行微调和维护所需的急切负载的额外工作。并且未能保持它们的优化肯定导致比使用NHibernate的延迟加载更差的性能。即使优化的预先加载也可能导致比加载所需数据更多的数据。

EF达到6版本就是这么做的。 (7,也许不是。)它的急切加载查询策略导致急切加载的结果集包含重复数据,只要“根”实体设置了对同一个加载的实体子实例的许多引用。 (虽然在我目前的知识状态下,我倾向于认为EF比NHibernate更有用的是关于渴望加载。但是很长一段时间我没有考虑和研究使用NHibernate的延迟加载,它的延迟加载比EF更有效率。)

(公平地说,延迟加载也可能会导致一些膨胀代码:如果要在关闭会话后使用实体,则挂起的延迟加载可能会导致失败。为了避免它们,NHibernateUtil.Initialize应该是在关闭会话之前调用所需的实体延迟关联。)

延迟加载的其他优化

NHibernate具有支持second level caching的内置功能。二级缓存允许缓存数据并在不同的NHibernate会话之间共享它们。

使用eager-loading,无法利用二级缓存从内存加载依赖实体(如果您使用内存cache provider进行二级缓存)。使用延迟加载最好利用二级缓存。

它是一个全功能的数据缓存,可自动处理数据失效。 (Provided you work with transactions。如果死锁阻止你这样做,也许你应该考虑在SQL Server上启用read committed snapshot模式,但这有点偏离主题。如果没有显式事务,缓存将被禁用为在您开始更新应用程序中的实体时。)

您只需要在全局配置(cache.provider_classcache.use_second_level_cache)中启用它,并在映射中声明可缓存的内容(在实体和/或实体集合上,使用<cache usage="..." />标记)。使用缓存区域设置到期日期。您甚至可以缓存查询(cache.use_query_cache,并指定查询是否可缓存)。请参阅here for an example

当然,对于您的情况,如果您的数据不符合缓存条件,则此功能无用。 (如果其他进程确实更新了您的数据,可能就是这种情况,而您不希望使用和配置SysCache2提供程序,它可以通过sql server通知任何数据更改。)

旁注

一个广为接受的麻烦解决方案意味着更多的工作。理想情况下,您的前端应用程序应该使用非规范化的数据副本,查询简单有效,而后台则保持规范化的数据库。