NHibernate添加子实体时按需创建父实体的性能优化问题
你遇到的这个问题在ORM关联操作里太常见了——每次加子实体都要走三次数据库往返,确实很拖性能。下面几个方案可以根据你的业务场景灵活选用:
1. 用Load替代Get,砍掉不必要的父实体查询
你现在用Session.Get<Parent>(id)会立即触发一条全量SELECT语句拉取父实体,但其实你只是要把子实体关联到它,根本不需要父实体的其他属性。换成Session.Load<Parent>(id)就不一样了:
var id = 1; Parent parent; try { parent = Session.Load<Parent>(id); // 这里不会触发SQL,返回代理对象 } catch (ObjectNotFoundException) { parent = new Parent(); } var child = new Child(); child.Parent = parent; parent.Children.Add(child); Session.Save(parent);
Load返回的是ORM的代理对象,只有当你访问父实体的非ID属性时才会触发查询。这个改动能直接砍掉第一条SELECT语句,只留下子实体插入和父实体的插入/更新(如果是新创建的话)。
2. 直接设置子实体的外键字段,完全绕过父实体加载
如果你的Child实体映射了ParentId外键字段(不是只映射导航属性Parent),这是最高效的方案——根本不需要加载父实体:
var child = new Child(); child.ParentId = 1; // 直接绑定外键ID Session.Save(child);
这样只会触发一条INSERT子实体的SQL,父实体完全不用参与操作。如果不确定父实体是否存在,可以先做个轻量的存在性检查:Session.QueryOver<Parent>().Where(p => p.Id == id).RowCount() > 0,这个COUNT查询比Get拉全量数据快得多。
3. 批量处理子实体,减少数据库往返次数
如果是一次性添加多个子实体,别循环着每次都处理父实体。先获取一次父实体(或代理),批量添加子实体后再一次性提交:
var id = 1; var parent = Session.Load<Parent>(id); var children = new List<Child>(); // 批量创建子实体 for (int i = 0; i < 100; i++) { var child = new Child(); child.Parent = parent; children.Add(child); parent.Children.Add(child); } Session.Save(parent);
配合ORM的批量配置(比如NHibernate里设置adonet.batch_size = 50),还能把多个INSERT合并成批量SQL,进一步减少数据库交互次数。
4. 利用缓存减少重复查询
如果父实体被频繁访问,可以依赖ORM的一级缓存(默认已开启)或者配置二级缓存,这样重复查询同一个父实体时,直接从缓存取,不用走数据库。不过这个方案更适合高频重复访问的场景,单次添加的优化效果不如前面几个。
内容的提问来源于stack exchange,提问作者Alexander

