現實世界常見多對多(M:M)關聯,如「老師-學生」、「使用者-群組」。資料庫中無法直接表達 M:M,必須透過中介表(Junction Table)轉換為兩組一對多(1:M)關聯。
以 User、Group、UserInGroup 三表為例:
-- User 表
CREATE TABLE [dbo].[User] (
[UserId] [nchar](10) NOT NULL PRIMARY KEY,
[UserName] [nchar](10) NULL
);
-- Group 表
CREATE TABLE [dbo].[Group] (
[GroupId] [nchar](10) NOT NULL PRIMARY KEY,
[GroupName] [nchar](10) NULL
);
-- 中介表 UserInGroup
CREATE TABLE [dbo].[UserInGroup] (
[UserId] [nchar](10) NOT NULL,
[GroupId] [nchar](10) NOT NULL,
PRIMARY KEY ([UserId], [GroupId]),
FOREIGN KEY ([UserId]) REFERENCES [dbo].[User]([UserId]),
FOREIGN KEY ([GroupId]) REFERENCES [dbo].[Group]([GroupId])
);

INSERT INTO [User] VALUES ('Tom Song', 'Tom Song');
INSERT INTO [User] VALUES ('Guoan Song', 'Guoan Song');
INSERT INTO [Group] VALUES ('Csharp', 'Csharp Team');
INSERT INTO [Group] VALUES ('Dlinq', 'Dlinq Team');
INSERT INTO UserInGroup VALUES ('Tom Song', 'Csharp');
INSERT INTO UserInGroup VALUES ('Tom Song', 'Dlinq');
INSERT INTO UserInGroup VALUES ('Guoan Song', 'Csharp');
INSERT INTO UserInGroup VALUES ('Guoan Song', 'Dlinq');
使用 OR Designer 產生實體後,User 與 Group 之間無直接導覽屬性,必須透過 UserInGroup 實體間接存取。若想在程式碼中直接使用 user.Groups 或 group.Users,可利用 partial class 擴充:
public partial class Group {
public List<User> Users {
get {
return (from u in this.UserInGroups select u.User).ToList();
}
}
}
public partial class User {
public List<Group> Groups {
get {
return (from g in this.UserInGroups select g.Group).ToList();
}
}
}
LINQ to Objects(記憶體查詢) 可直接使用上述擴充屬性:
var userSet = db.Users.ToList(); // 先取回記憶體
foreach (var u in userSet)
foreach (var g in u.Groups) // OK:在記憶體中運算
Console.WriteLine(g.GroupName);
LINQ to SQL(轉譯為 SQL) 則會失敗,因為無法將擴充屬性轉譯為有效的 SQL JOIN:
// 編譯通過,執行時期拋出例外
var q = (from u in db.Users
from g in u.Groups // 無法轉譯為 SQL
select new { u.UserName, g.GroupName }).ToList();
解決方案:先 .ToList() 將資料載入記憶體,再進行後續查詢(轉為 LINQ to Objects):
var q2 = (from u1 in (from u in db.Users select u).ToList()
from g in u1.Groups
select new { u1.UserName, g.GroupName }).ToList(); // 正常執行
將擴充屬性型別改為 IEnumerable<T> 並移除 .ToList(),可獲得延遲執行特性(逐筆產生),記憶體效率較佳:
public partial class Group {
public IEnumerable<User> Users {
get { return from u in this.UserInGroups select u.User; }
}
}
public partial class User {
public IEnumerable<Group> Groups {
get { return from g in this.UserInGroups select g.Group; }
}
}
差異總結: List<T> 版本會一次性載入所有關聯資料;IEnumerable<T> 版本則按需逐筆產生,適合大量資料場景。