存取追蹤實體
存取 所 DbContext 追蹤的實體有四個主要 API:
- DbContext.Entry 傳 EntityEntry<TEntity> 回指定實體實例的實例。
- ChangeTracker.Entries 會 EntityEntry<TEntity> 傳回所有追蹤實體的實例,或針對指定型別的所有追蹤實體傳回實例。
- DbContext.Find、 DbContext.FindAsync 、 DbSet<TEntity>.Find 和 DbSet<TEntity>.FindAsync 會依主鍵尋找單一實體,先尋找追蹤的實體,然後視需要查詢資料庫。
- DbSet<TEntity>.Local 會傳回 DbSet 所表示實體類型實體的實際實體(而非 EntityEntry 實例)。
以下各節會更詳細地說明上述各項。
提示
本檔假設瞭解實體狀態和 EF Core 變更追蹤的基本概念。 如需這些主題的詳細資訊,請參閱 EF Core 中的變更追蹤。
提示
您可以從 GitHub 下載範例程式碼,以執行並偵錯此文件中的所有程式碼。
使用 DbCoNtext.Entry 和 EntityEntry 實例
針對每個追蹤的實體,Entity Framework Core (EF Core) 會追蹤:
- 實體的整體狀態。 這是 、、 或 之
Unchanged
一;如需詳細資訊,請參閱 EF Core 中的Deleted
變更追蹤。Added
Modified
- 追蹤實體之間的關聯性。 例如,文章所屬的部落格。
- 屬性的「目前值」。
- 當此資訊可供使用時,屬性的「原始值」。 原始值是從資料庫查詢實體時所存在的屬性值。
- 自從查詢這些屬性值之後,已經修改了哪些屬性值。
- 屬性值的其他資訊,例如值是否為 暫時性 。
傳遞實體實例,以 DbContext.Entry 產生 EntityEntry<TEntity> 提供指定實體此資訊的存取權。 例如:
using var context = new BlogsContext();
var blog = context.Blogs.Single(e => e.Id == 1);
var entityEntry = context.Entry(blog);
下列各節說明如何使用 EntityEntry 來存取和操作實體狀態,以及實體屬性和導覽的狀態。
使用實體
最常見的用法 EntityEntry<TEntity> 是存取實體的目前 EntityState 。 例如:
var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
context.Entry(blog).State = EntityState.Modified;
}
Entry 方法也可以在尚未追蹤的實體上使用。 這 不會開始追蹤實體 ;實體的狀態仍然是 Detached
。 不過,傳回的 EntityEntry 接著可用來變更實體狀態,此時實體將會在指定的狀態中追蹤。 例如,下列程式碼會開始將部落格實例追蹤為 Added
:
var newBlog = new Blog();
Debug.Assert(context.Entry(newBlog).State == EntityState.Detached);
context.Entry(newBlog).State = EntityState.Added;
Debug.Assert(context.Entry(newBlog).State == EntityState.Added);
提示
不同于 EF6,設定個別實體的狀態不會造成追蹤所有已連線的實體。 如此一來,設定狀態會比呼叫 、 Attach
或 Update
,以這種方式在 Add
實體的整個圖形上運作。
下表摘要說明如何使用 EntityEntry 來處理整個實體的方法:
EntityEntry 成員 | 描述 |
---|---|
EntityEntry.State | 取得和設定 EntityState 實體的 。 |
EntityEntry.Entity | 取得實體實例。 |
EntityEntry.Context | DbContext正在追蹤此實體的 。 |
EntityEntry.Metadata | IEntityType 實體類型的中繼資料。 |
EntityEntry.IsKeySet | 實體是否已設定其索引鍵值。 |
EntityEntry.Reload() | 以從資料庫讀取的值覆寫屬性值。 |
EntityEntry.DetectChanges() | 僅強制偵測此實體的變更;請參閱 變更偵測和通知 。 |
使用單一屬性
數個 EntityEntry<TEntity>.Property 多載允許存取實體個別屬性的相關資訊。 例如,使用強型別的 fluent-like API:
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);
屬性名稱可以改為以字串的形式傳遞。 例如:
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");
然後,傳回的 PropertyEntry<TEntity,TProperty> 可用來存取 屬性的相關資訊。 例如,它可以用來取得並設定此實體上屬性的目前值:
string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";
上述兩個屬性方法都會傳回強型別泛型 PropertyEntry<TEntity,TProperty> 實例。 使用這個泛型型別是慣用的,因為它允許存取屬性值,而不需要 Boxing 實值型別 。 不過,如果在編譯階段不知道實體或屬性的類型,則可以改為取得非泛型 PropertyEntry :
PropertyEntry propertyEntry = context.Entry(blog).Property("Name");
這允許存取任何屬性的屬性資訊,不論其類型為何,都會犧牲 Boxing 實值型別。 例如:
object blog = context.Blogs.Single(e => e.Id == 1);
object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";
下表摘要說明 PropertyEntry 所公開的屬性資訊:
PropertyEntry 成員 | 描述 |
---|---|
PropertyEntry<TEntity,TProperty>.CurrentValue | 取得和設定 屬性的目前值。 |
PropertyEntry<TEntity,TProperty>.OriginalValue | 取得並設定屬性的原始值,如果有的話。 |
PropertyEntry<TEntity,TProperty>.EntityEntry | 實體的 EntityEntry<TEntity> 返回參考。 |
PropertyEntry.Metadata | IProperty 屬性的中繼資料。 |
PropertyEntry.IsModified | 指出這個屬性是否標示為已修改,並允許變更此狀態。 |
PropertyEntry.IsTemporary | 指出這個屬性是否標示為 暫時性 ,並允許變更此狀態。 |
注意:
- 屬性的原始值是屬性從資料庫查詢實體時具有的值。 不過,如果實體已中斷連線,然後明確附加至另一個 DbCoNtext,例如 搭配
Attach
或Update
,則無法使用原始值。 在此情況下,傳回的原始值會與目前值相同。 - SaveChanges 只會更新標示為已修改的屬性。 設定 IsModified 為 true 以強制 EF Core 更新指定的屬性值,或將它設定為 false,以防止 EF Core 更新屬性值。
- 暫時值 通常是由 EF Core 值產生器 所產生。 設定屬性的目前值會將暫存值取代為指定的值,並將屬性標示為非暫時值。 設定 IsTemporary 為 true,強制值在明確設定之後為暫時。
使用單一導覽
、 和 EntityEntry.Navigation 的 EntityEntry<TEntity>.Reference EntityEntry<TEntity>.Collection 數個多載允許存取個別導覽的相關資訊。
透過 方法存取單一相關實體的 Reference 參考導覽。 參考導覽指向一對多關聯性的「一」端,以及一對一關聯性的兩端。 例如:
ReferenceEntry<Post, Blog> referenceEntry1 = context.Entry(post).Reference(e => e.Blog);
ReferenceEntry<Post, Blog> referenceEntry2 = context.Entry(post).Reference<Blog>("Blog");
ReferenceEntry referenceEntry3 = context.Entry(post).Reference("Blog");
當用於一對多和多對多關聯性的「多對多」端時,流覽也可以是相關實體的集合。 方法 Collection 可用來存取集合導覽。 例如:
CollectionEntry<Blog, Post> collectionEntry1 = context.Entry(blog).Collection(e => e.Posts);
CollectionEntry<Blog, Post> collectionEntry2 = context.Entry(blog).Collection<Post>("Posts");
CollectionEntry collectionEntry3 = context.Entry(blog).Collection("Posts");
某些作業適用于所有導覽。 您可以使用 方法來存取這些參考和集合導覽 EntityEntry.Navigation 。 請注意,一起存取所有導覽時,只能使用非泛型存取。 例如:
NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");
下表摘要說明使用 ReferenceEntry<TEntity,TProperty> 、 CollectionEntry<TEntity,TRelatedEntity> 和 NavigationEntry 的方法:
NavigationEntry 成員 | 描述 |
---|---|
MemberEntry.CurrentValue | 取得和設定巡覽的目前值。 這是集合導覽的整個集合。 |
NavigationEntry.Metadata | INavigationBase 導覽的中繼資料。 |
NavigationEntry.IsLoaded | 取得或設定值,指出是否已從資料庫完整載入相關的實體或集合。 |
NavigationEntry.Load() | 從資料庫載入相關的實體或集合;請參閱 明確載入相關資料 。 |
NavigationEntry.Query() | EF Core 查詢會用來將此導覽載入為 IQueryable 可進一步撰寫的 ;請參閱 明確載入相關資料 。 |
使用實體的所有屬性
EntityEntry.Properties 針對 IEnumerable<T> PropertyEntry 實體的每個屬性,傳回 的 。 這可用來針對實體的每個屬性執行動作。 例如,若要將任何 DateTime 屬性設定為 DateTime.Now
:
foreach (var propertyEntry in context.Entry(blog).Properties)
{
if (propertyEntry.Metadata.ClrType == typeof(DateTime))
{
propertyEntry.CurrentValue = DateTime.Now;
}
}
此外,EntityEntry 也包含數種方法,可同時取得和設定所有屬性值。 這些方法會使用 PropertyValues 類別,代表屬性及其值的集合。 PropertyValues 可以取得目前或原始值,或目前儲存在資料庫中的值。 例如:
var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = context.Entry(blog).GetDatabaseValues();
這些 PropertyValues 物件本身並不十分有用。 不過,可以結合它們來執行操作實體時所需的一般作業。 這在處理資料傳輸物件時以及解決 開放式並行衝突 時很有用。 下列各節顯示一些範例。
從實體或 DTO 設定目前或原始值
從另一個物件複製值,即可更新實體的目前或原始值。 例如,請考慮 BlogDto
具有與實體類型相同屬性的資料傳輸物件 (DTO):
public class BlogDto
{
public int Id { get; set; }
public string Name { get; set; }
}
這可用來使用 PropertyValues.SetValues 來設定追蹤實體的目前值:
var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDto);
使用從服務呼叫或多層式應用程式中用戶端取得的值來更新實體時,有時會使用這項技術。 請注意,使用的物件不一定與實體的類型相同,只要其名稱符合實體的屬性。 在上述範例中,會使用 DTO BlogDto
的實例來設定追蹤 Blog
實體的目前值。
請注意,只有在值集與目前值不同時,才會將屬性標示為修改。
設定字典中的目前或原始值
上述範例會設定實體或 DTO 實例的值。 當屬性值儲存為字典中的名稱/值組時,可以使用相同的行為。 例如:
var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDictionary);
從資料庫設定目前或原始值
實體的目前或原始值可以使用資料庫中的最新值來更新,方法是呼叫 GetDatabaseValues() 或 GetDatabaseValuesAsync ,以及使用傳回的物件來設定目前或原始值,或兩者。 例如:
var databaseValues = context.Entry(blog).GetDatabaseValues();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);
建立包含目前、原始或資料庫值的複製物件
從 CurrentValues、OriginalValues 或 GetDatabaseValues 傳回的 PropertyValues 物件可用來使用 PropertyValues.ToObject() 建立實體的複製品。 例如:
var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();
請注意, ToObject
傳回 DbCoNtext 未追蹤的新實例。 傳回的物件也不會將任何關聯性設定為其他實體。
複製的物件可用於解決與資料庫並行更新相關的問題,特別是當資料系結至特定類型的物件時。 如需詳細資訊,請參閱 開放式並行 存取。
使用實體的所有導覽
EntityEntry.Navigations 會 IEnumerable<T> NavigationEntry 針對實體的每個導覽傳回 的 。 EntityEntry.References 和 EntityEntry.Collections 會執行相同動作,但僅限於參考或集合導覽。 這可用來針對實體的每個導覽執行動作。 例如,若要強制載入所有相關實體:
foreach (var navigationEntry in context.Entry(blog).Navigations)
{
navigationEntry.Load();
}
使用實體的所有成員
一般屬性和導覽屬性有不同的狀態和行為。 因此,處理導覽和非導覽很常見,如上述各節所示。 不過,不論實體是一般屬性還是導覽,有時候對實體的任何成員執行某些動作會很有用。 EntityEntry.Member 和 EntityEntry.Members 會針對此目的提供。 例如:
foreach (var memberEntry in context.Entry(blog).Members)
{
Console.WriteLine(
$"Member {memberEntry.Metadata.Name} is of type {memberEntry.Metadata.ClrType.ShortDisplayName()} and has value {memberEntry.CurrentValue}");
}
在範例的部落格上執行此程式碼會產生下列輸出:
Member Id is of type int and has value 1
Member Name is of type string and has value .NET Blog
Member Posts is of type IList<Post> and has value System.Collections.Generic.List`1[Post]
提示
變更 追蹤器偵錯檢視 會顯示如下的資訊。 整個變更追蹤器的偵錯檢視是從每個追蹤實體的個別 EntityEntry.DebugView 產生。
尋找和 FindAsync
DbContext.Find、 DbContext.FindAsync 、 DbSet<TEntity>.Find 和 DbSet<TEntity>.FindAsync 是專為在已知單一實體的主鍵時有效率地查閱所設計。 尋找第一次檢查實體是否已追蹤,如果是,則立即傳回實體。 只有在實體未在本機追蹤時,才會進行資料庫查詢。 例如,請考慮針對相同實體呼叫 Find 兩次的程式碼:
using var context = new BlogsContext();
Console.WriteLine("First call to Find...");
var blog1 = context.Blogs.Find(1);
Console.WriteLine($"...found blog {blog1.Name}");
Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = context.Blogs.Find(1);
Debug.Assert(blog1 == blog2);
Console.WriteLine("...returned the same instance without executing a query.");
使用 SQLite 時,此程式碼的輸出(包括 EF Core 記錄) 為:
First call to Find...
info: 12/29/2020 07:45:53.682 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[@__p_0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
SELECT "b"."Id", "b"."Name"
FROM "Blogs" AS "b"
WHERE "b"."Id" = @__p_0
LIMIT 1
...found blog .NET Blog
Second call to Find...
...returned the same instance without executing a query.
請注意,第一次呼叫在本機找不到實體,因此會執行資料庫查詢。 相反地,第二個呼叫會傳回相同的實例,而不查詢資料庫,因為它已經追蹤。
如果具有指定索引鍵的實體未在本機追蹤且不存在於資料庫中,則尋找 會傳回 null。
複合索引鍵
Find 也可以搭配複合索引鍵使用。 例如,假設有 OrderLine
複合索引鍵的實體,其中包含訂單識別碼和產品識別碼:
public class OrderLine
{
public int OrderId { get; set; }
public int ProductId { get; set; }
//...
}
複合索引鍵必須設定為 DbContext.OnModelCreating 定義索引鍵部分 及其順序 。 例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<OrderLine>()
.HasKey(e => new { e.OrderId, e.ProductId });
}
請注意, OrderId
是索引鍵的第一個部分,而 ProductId
是索引鍵的第二個部分。 將索引鍵值傳遞至 Find 時,必須使用這個順序。 例如:
var orderline = context.OrderLines.Find(orderId, productId);
使用 ChangeTracker.Entries 存取所有追蹤的實體
到目前為止,我們一次只能存取一個 EntityEntry 。 ChangeTracker.Entries() 會針對 DbCoNtext 目前追蹤的每個實體,傳回 EntityEntry。 例如:
using var context = new BlogsContext();
var blogs = context.Blogs.Include(e => e.Posts).ToList();
foreach (var entityEntry in context.ChangeTracker.Entries())
{
Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}
此程式碼會產生下列輸出:
Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2
請注意,會傳回部落格和文章的專案。 您可以改用 ChangeTracker.Entries<TEntity>() 泛型多載,將結果篩選為特定實體類型:
foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
此程式碼的輸出會顯示只會傳回貼文:
Found Post entity with ID 1
Found Post entity with ID 2
此外,使用泛型多載會傳回泛型 EntityEntry<TEntity> 實例。 這是允許在此範例中存取 Id
屬性的類似 Fluent。
用於篩選的泛型型別不一定是對應的實體類型;您可以改用未對應的基底類型或介面。 例如,如果模型中的所有實體類型實作定義其索引鍵屬性的介面:
public interface IEntityWithKey
{
int Id { get; set; }
}
然後,這個介面可以用來以強型別的方式處理任何追蹤實體的索引鍵。 例如:
foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
使用 DbSet.Local 查詢追蹤的實體
EF Core 查詢一律會在資料庫上執行,而且只會傳回已儲存至資料庫的實體。 DbSet<TEntity>.Local 提供機制來查詢 DbCoNtext 以取得本機、追蹤的實體。
由於 DbSet.Local
用來查詢追蹤的實體,因此通常會將實體載入 DbCoNtext,然後使用這些載入的實體。 這特別適用于資料系結,但在其他情況下也很有用。 例如,在下列程式碼中,資料庫會先針對所有部落格和文章進行查詢。 擴充 Load 方法可用來使用內容追蹤的結果來執行此查詢,而不會直接傳回至應用程式。 (使用 ToList
或類似專案的效果相同,但建立傳回清單的額外負荷,這裡不需要此清單。此範例接著會使用 DbSet.Local
來存取本機追蹤的實體:
using var context = new BlogsContext();
context.Blogs.Include(e => e.Posts).Load();
foreach (var blog in context.Blogs.Local)
{
Console.WriteLine($"Blog: {blog.Name}");
}
foreach (var post in context.Posts.Local)
{
Console.WriteLine($"Post: {post.Title}");
}
請注意,不同于 ChangeTracker.Entries() ,會 DbSet.Local
直接傳回實體實例。 當然,您可以呼叫 DbContext.Entry 來取得傳回實體的 EntityEntry。
本機檢視
DbSet<TEntity>.Local 會傳回反映這些實體目前 EntityState 之本機追蹤實體的檢視。 具體來說,這表示:
Added
包含實體。 請注意,這並非一般 EF Core 查詢的情況,因為Added
資料庫中尚未存在實體,因此不會由資料庫查詢傳回。Deleted
實體會排除。 請注意,這再次不是一般 EF Core 查詢的情況,因為Deleted
實體仍然存在於資料庫中,因此資料庫查詢會 傳回。
這 DbSet.Local
表示會檢視反映實體圖形目前概念狀態的資料,其中包含 Added
實體和 Deleted
排除的實體。 這會比對呼叫 SaveChanges 之後預期的資料庫狀態。
這通常是資料系結的理想檢視,因為它會根據應用程式所做的變更,向使用者呈現資料。
下列程式碼將一個貼文標示為 Deleted
,然後新增一個貼文,並將其標示為 ,藉 Added
以示範這一點:
using var context = new BlogsContext();
var posts = context.Posts.Include(e => e.Blog).ToList();
Console.WriteLine("Local view after loading posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
context.Remove(posts[1]);
context.Add(
new Post
{
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many...",
Blog = posts[0].Blog
});
Console.WriteLine("Local view after adding and deleting posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
此程式碼的輸出如下:
Local view after loading posts:
Post: Announcing the Release of EF Core 5.0
Post: Announcing F# 5
Post: Announcing .NET 5.0
Local view after adding and deleting posts:
Post: What’s next for System.Text.Json?
Post: Announcing the Release of EF Core 5.0
Post: Announcing .NET 5.0
請注意,已刪除的貼文會從本機檢視中移除,並包含新增的貼文。
使用 Local 新增和移除實體
DbSet<TEntity>.Local 會傳回 LocalView<TEntity> 的執行個體。 這是 的 ICollection<T> 實作,會在從集合新增和移除實體時產生和回應通知。 (這個概念與 ObservableCollection<T> 相同,但實作為對現有 EF Core 變更追蹤專案的投影,而不是獨立集合。
本機檢視的通知會連結至 DbCoNtext 變更追蹤,讓本機檢視與 DbCoNtext 保持同步。 具體而言:
- 新增實體以
DbSet.Local
讓 DbCoNtext 追蹤它,通常是處於Added
狀態。 (如果實體已經有產生的索引鍵值,則會改為追蹤Unchanged
它。 - 從 中移除實體
DbSet.Local
會導致實體標示為Deleted
。 - DbCoNtext 所追蹤的實體會自動出現在集合中
DbSet.Local
。 例如,執行查詢以帶入更多實體時,會自動更新本機檢視。 - 標示為
Deleted
的實體會自動從本機集合中移除。
這表示本機檢視可用來操作追蹤實體,只要從集合新增和移除即可。 例如,讓我們修改先前的範例程式碼,以從本機集合新增和移除貼文:
using var context = new BlogsContext();
var posts = context.Posts.Include(e => e.Blog).ToList();
Console.WriteLine("Local view after loading posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
context.Posts.Local.Remove(posts[1]);
context.Posts.Local.Add(
new Post
{
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many...",
Blog = posts[0].Blog
});
Console.WriteLine("Local view after adding and deleting posts:");
foreach (var post in context.Posts.Local)
{
Console.WriteLine($" Post: {post.Title}");
}
因為對本機檢視所做的變更會與 DbCoNtext 同步,因此輸出會與上一個範例維持不變。
使用 Windows Forms 或 WPF 資料系結的本機檢視
DbSet<TEntity>.Local 會形成資料系結至 EF Core 實體的基礎。 不過,Windows Forms 和 WPF 在搭配特定類型的通知集合使用時效果最佳。 本機檢視支援建立這些特定的集合類型:
- LocalView<TEntity>.ToObservableCollection() 傳 ObservableCollection<T> 回 WPF 資料系結的 。
- LocalView<TEntity>.ToBindingList() 會 BindingList<T> 傳回 Windows Forms 資料系結的 。
例如:
ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();
如需使用 EF Core 進行 WPF 資料系結的詳細資訊,請參閱 開始使用 WPF 資料系結和開始使用 Windows Forms 以取得使用 EF Core 進行 Windows Forms 資料系結的詳細資訊。
提示
第一次存取並快取時,會延遲建立指定 DbSet 實例的本機檢視。 LocalView 建立本身的速度很快,而且不會使用大量的記憶體。 不過,它會呼叫 DetectChanges ,這對大量實體而言可能很慢。 和 ToBindingList
所 ToObservableCollection
建立的集合也會延遲建立,然後快取。 這兩種方法都會建立新的集合,當涉及數千個實體時,可能會變慢並使用大量的記憶體。