NHibernate 多對多對映的資料更新
最近在用 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
是更好的選擇。
相關文章
- 初試 Entity Framework Core 的多對多對映Framework
- Spring Data JPA 之 一對一,一對多,多對多 關係對映Spring
- mybatis入門基礎(六)----高階對映(一對一,一對多,多對多)MyBatis
- hibernate(四) 雙向多對多對映關係
- Spring Boot 入門系列(二十八) JPA 的實體對映關係,一對一,一對多,多對多關係對映!Spring Boot
- Mybatis學習筆記(5)-高階對映之多對多對映MyBatis筆記
- JPA中對映關係詳細說明(一對多,多對一,一對一、多對多)、@JoinColumn、mappedBy說明APP
- hibernate(三) 一對多對映關係
- Mybatis學習筆記(4)-高階對映之一對多對映MyBatis筆記
- EF:Fluent API 把一對多對映為一對一API
- Hibernate對映檔案一對多關係薦
- mybatis的一對多,多對一,以及多對對的配置和使用MyBatis
- 多對多關聯 attach() 相同的資料包錯
- MyBatis表關聯 一對多 多對一 多對多MyBatis
- MyBatis從入門到精通(十一):MyBatis高階結果對映之一對多對映MyBatis
- Hibernate繼承對映多型的詳解繼承多型
- Mybatis【一對多、多對一、多對多】知識要點MyBatis
- NHibernate VS .NET Type VS DbType 的型別對映關係型別
- 資料結構-對映資料結構
- JavaEE MyBatis關聯對映之多對多(教材學習筆記)JavaMyBatis筆記
- 資料庫多對多表關係資料資料庫
- MyBatis 查詢資料時屬性中多對一的問題(多條資料對應一條資料)MyBatis
- JPA(3) 表關聯關係(多對一、一對多、多對多、一對一)
- gorm 關係一對一,一對多,多對多查詢GoORM
- 多視角三維模型紋理對映 01模型
- MyBatis加強(1)~myBatis物件關係對映(多對一關係、一對多關係)、延遲/懶載入MyBatis物件
- 多對多的操作問題
- Mybatis一對多、多對一處理MyBatis
- mybatis一對多&&多對一處理MyBatis
- MyBatis07-(多對一、一對多)MyBatis
- Mybatis09_一對一、一對多、多對多、延遲載入MyBatis
- mybatis中一對多對映,但兩張表id相同怎麼辦?MyBatis
- spring data jpa關聯查詢(一對一、一對多、多對多)Spring
- AutoMapper在MVC中的運用05-對映中的忽略、處理null、多種對映轉換APPMVCNull
- 2018-02-16-3.mybatis一對多對映情況解析MyBatis
- Hibernate多對多示例
- C# 利用 DbUp 通過多個SQL Script檔案完成對資料庫的更新C#SQL資料庫
- Hibernate annotation, JPA如何對映多個屬性為unique