文档章节

NHibernate 多对多映射的数据更新

李朝强
 李朝强
发布于 2016/02/17 12:33
字数 939
阅读 38
收藏 1

最近在用 NHibernate 做多对多更新时突然发现 NHibernate 更新的策略很差, 对多对多关系的更新居然是先全部删除再插入全部数据, 感觉非常奇怪, 现在还原如下:

原来的实体类关系如下:

public class User { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual ICollection<Role> Roles { get; set; } public User() {
        Roles = new HashSet<Role>();
    }
} public class Role { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual ICollection<User> Users { get; set; } public Role() {
        Users = new HashSet<User>();
    }

}

即一个用户可以有多个角色, 一个角色也可以有多个人, 典型的多对多关系, 对应的映射代码如下:

public class UserMapping : ClassMapping<User> { public UserMapping() {
        Table("[User]");

        Id(m => m.Id, map => { map.Column("[Id]"); map.Type(NHibernateUtil.Int32); map.Generator(Generators.Identity);
        });

        Property(m => m.Name, map => { map.Column("[Name]"); map.Type(NHibernateUtil.String);
        });

        Bag(
            m => m.Roles, map => { map.Table("[User_Role]"); map.Key(k => { k.Column("[UserId]"); });
            },
            rel => {
                rel.ManyToMany(map => { map.Class(typeof(Role)); map.Column("[RoleId]");
                });
            }
        );
    }
} public class RoleMapping : ClassMapping<Role> { public RoleMapping() {
        Table("[Role]");

        Id(m => m.Id, map => { map.Column("[Id]"); map.Type(NHibernateUtil.Int32); map.Generator(Generators.Identity);
        });

        Property(m => m.Name, map => { map.Column("[Name]"); map.Type(NHibernateUtil.String);
        });


        Bag(
            m => m.Users, map => { map.Table("[User_Role]"); map.Key(k => { k.Column("[RoleId]"); }); map.Inverse(true);
            },
            rel => {
                rel.ManyToMany(map => { map.Class(typeof(User)); map.Column("[UserId]");
                });
            }
        );

    }
}

数据库关系图如下:

当向用户添加或删除角色是, 发现更新的效率特别低, 代码如下:

using (var session = sessionFactory.OpenSession()) { var user = session.Query<User>().First(); var firstRole = user.Roles.First();
    user.Roles.Remove(firstRole);
    session.Update(user); var roleCount = session.Query<Role>().Count(); var role = new Role { Name = "Role " + (roleCount + 1) };
    session.Save(role);

    user.Roles.Add(role);
    session.Update(user);

    session.Update(user);
    session.Flush();
}

上面的代码是将用户的第一个角色删除, 再添加一个新的角色, NHibernate 生成的 SQL 语句如下(仅包含对关系表User_Role的操作):

DELETE FROM [User_Role] WHERE [UserId] = @p0;@p0 = 1 [Type: Int32 (0)] INSERT INTO [User_Role]  ([UserId], [RoleId]) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 2 [Type: Int32 (0)] INSERT INTO [User_Role]  ([UserId], [RoleId]) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 7 [Type: Int32 (0)] INSERT INTO [User_Role]  ([UserId], [RoleId]) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 6 [Type: Int32 (0)] INSERT INTO [User_Role]  ([UserId], [RoleId]) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 10 [Type: Int32 (0)]

居然是先将属于该用户的全部角色删除, 再添加一份新的进来, 完全无法接受, 反过来思考觉得肯定是自己的问题, 经过一番搜索 (Google), 发现 StackOverflow 上也有人问类似的问题, 并且最终在 NHibernate Tip: Use set for many-to-many associations 发现了解决方案, 将多对多的映射的bag改为用set, 问题终于得到了解决, 改过后的映射如下:

Set(
    m => m.Roles, map => { map.Table("[User_Role]"); map.Key(k => { k.Column("[UserId]"); });
    },
    rel => {
        rel.ManyToMany(map => { map.Class(typeof(Role)); map.Column("[RoleId]");
        });
    }
);

将UserMapping和RoleMapping中多对多映射全部改为Set之后, 上面的测试代码生成的 SQL 如下:

DELETE FROM [User_Role] WHERE [UserId] = @p0 AND [RoleId] = @p1;@p0 = 1 [Type: Int32 (0)], @p1 = 8 [Type: Int32 (0)] INSERT INTO [User_Role]  ([UserId], [RoleId]) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 9 [Type: Int32 (0)]

在 NHibernate 参考文档的 19.5. Understanding Collection performance 中这样描述:

Bags are the worst case. Since a bag permits duplicate element values and has no index column, no primary key may be defined. NHibernate has no way of distinguishing between duplicate rows. NHibernate resolves this problem by completely removing (in a single DELETE) and recreating the collection whenever it changes. This might be very inefficient.

不只是多对多, 如果你的集合需要更新, NHibernate 推荐的是:

19.5.2. Lists, maps, idbags and sets are the most efficient collections to update

然而 bags 也不是一无是处:

19.5.3. Bags and lists are the most efficient inverse collections

Just before you ditch bags forever, there is a particular case in which bags (and also lists) are much more performant than sets. For a collection with inverse="true" (the standard bidirectional one-to-many relationship idiom, for example) we can add elements to a bag or list without needing to initialize (fetch) the bag elements! This is because IList.Add() must always succeed for a bag or IList (unlike an ISet). This can make the following common code much faster.

Parent p = sess.Load(id); Child c = new Child(); c.Parent = p;
p.Children.Add(c); //no need to fetch the collection! sess.Flush();

由此可见,bag在多对多映射更新时性能较差, 如果不需要更新,则可以放心使用, 在需要更新时则set是更好的选择。

本文转载自:http://beginor.github.io/2015/03/17/nhibernate-many-to-many-update.html

共有 人打赏支持
李朝强
粉丝 84
博文 289
码字总数 148281
作品 0
郑州
产品经理
私信 提问
Visual Entity 下载列表(最新版为 1.8.0 ,已支持Postgre&DB2,即将支持 Sysbase ASE)

发展线路: 2.x 将支持的功能有: 1、增加重构功能。当修改类或属性名称,提示并进行重构以更新所有引用。 2、允许将自定义的特性放在属性窗口中,以方便编辑。 3、Web Services 代码生成。 ...

长平狐
2013/06/17
313
1
NHibernate从入门到精通系列(1)——NHibernate概括

内容摘要 NHibernate简介 ORM简介 NHibernate优缺点 一、NHibernate简介 什么是?NHibernate?NHibernate是一个面向.NET环境的对象/关系数据库映射工具。对象/关系数据库映射(object/relati...

长平狐
2012/06/11
576
0
比较 NHibernate 和实体框架

葡萄牙的一位开发者 Ricardo Peres 最近发布了一篇文章,以看起来无偏见的形式对领先的两种 .NET ORM:NHibernate 和实体框架进行了比较。 我们建议考虑使用这两种框架的人都应该读下他的文章...

墙头草
2012/06/18
3.6K
4
NHibernate 快速入门

这篇教程将透过你的第一个NHibernate项目,讲述配置和映射所需要的基本步骤。 仅关注我们想要的,我们将从一个很简单的例子开始。为了让代码量最小,我们也去掉了单元测试和接口的代码,专一...

徐继开
2013/07/12
15.7K
25
NHibernate 5.0 发布,对象关系映射解决方案

NHibernate 是一个基于.Net 的针对关系型数据库的对象持久化类库。Nhibernate 来源于非常优秀的基于Java的Hibernate 关系型持久化工具。NHibernate 从数据库底层来持久化你的.Net 对象到关系...

周其
2017/10/16
1K
6

没有更多内容

加载失败,请刷新页面

加载更多

RestClientUtil和ConfigRestClientUtil区别说明

RestClientUtil directly executes the DSL defined in the code. ConfigRestClientUtil gets the DSL defined in the configuration file by the DSL name and executes it. RestClientUtil......

bboss
今天
11
0

中国龙-扬科
昨天
2
0
Linux系统设置全局的默认网络代理

更改全局配置文件/etc/profile all_proxy="all_proxy=socks://rahowviahva.ml:80/"ftp_proxy="ftp_proxy=http://rahowviahva.ml:80/"http_proxy="http_proxy=http://rahowviahva.ml:80/"......

临江仙卜算子
昨天
9
0
java框架学习日志-6(bean作用域和自动装配)

本章补充bean的作用域和自动装配 bean作用域 之前提到可以用scope来设置单例模式 <bean id="type" class="cn.dota2.tpye.Type" scope="singleton"></bean> 除此之外还有几种用法 singleton:......

白话
昨天
8
0
在PC上测试移动端网站和模拟手机浏览器的5大方法

总结很全面,保存下来以备不时之需。原文地址:https://www.cnblogs.com/coolfeng/p/4708942.html

kitty1116
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部