本機資料

直接對 DbSet 執行 LINQ 查詢一律會將查詢傳送至資料庫,但您可以使用 DbSet.Local 屬性存取目前記憶體中的資料。 您也可以使用 DbCoNtext.Entry 和 DbCoNtext.ChangeTracker.Entries 方法來存取 EF 正在追蹤實體的額外資訊。 本主題所示範的技巧同樣適用於使用 Code First 和 EF 設計工具所建立的模型。

使用本機來查看本機資料

DbSet 的 Local 屬性可讓您輕鬆存取目前由內容追蹤且尚未標示為 Deleted 之集合的實體。 存取 Local 屬性絕不會導致查詢傳送至資料庫。 這表示通常會在查詢執行之後使用。 Load 擴充方法可用來執行查詢,讓內容追蹤結果。 例如:

using (var context = new BloggingContext())
{
    // Load all blogs from the database into the context
    context.Blogs.Load();

    // Add a new blog to the context
    context.Blogs.Add(new Blog { Name = "My New Blog" });

    // Mark one of the existing blogs as Deleted
    context.Blogs.Remove(context.Blogs.Find(1));

    // Loop over the blogs in the context.
    Console.WriteLine("In Local: ");
    foreach (var blog in context.Blogs.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }

    // Perform a query against the database.
    Console.WriteLine("\nIn DbSet query: ");
    foreach (var blog in context.Blogs)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }
}

如果我們在資料庫中有兩個部落格 - 'ADO.NET Blog' 與 BlogId 為 1 和 'Visual Studio 部落格' 與部落格識別碼為 2 - 我們可以預期下列輸出:

In Local:
Found 0: My New Blog with state Added
Found 2: The Visual Studio Blog with state Unchanged

In DbSet query:
Found 1: ADO.NET Blog with state Deleted
Found 2: The Visual Studio Blog with state Unchanged

這說明三點:

  • 新的部落格「我的新部落格」已包含在本機集合中,即使尚未儲存至資料庫也一樣。 此部落格的主鍵為零,因為資料庫尚未產生實體的實際索引鍵。
  • 即使內容仍在追蹤「ADO.NET 部落格」,仍不會包含在本機集合中。 這是因為我們已將它從 DbSet 中移除,因此會將它標示為已刪除。
  • 當 DbSet 用來執行查詢時,標示要刪除的部落格 (ADO.NET Blog) 會包含在結果中,而尚未儲存至資料庫的新部落格 (My New Blog) 則不會包含在結果中。 這是因為 DbSet 正在對資料庫執行查詢,而傳回的結果一律會反映資料庫中的內容。

使用 Local 從內容新增和移除實體

DbSet 上的 Local 屬性會 傳回 ObservableCollection ,並連結事件,使其與內容的內容保持同步。 這表示可以從 Local 集合或 DbSet 新增或移除實體。 這也表示,將新實體帶入內容的查詢會導致使用這些實體更新 Local 集合。 例如:

using (var context = new BloggingContext())
{
    // Load some posts from the database into the context
    context.Posts.Where(p => p.Tags.Contains("entity-framework")).Load();  

    // Get the local collection and make some changes to it
    var localPosts = context.Posts.Local;
    localPosts.Add(new Post { Name = "What's New in EF" });
    localPosts.Remove(context.Posts.Find(1));  

    // Loop over the posts in the context.
    Console.WriteLine("In Local after entity-framework query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }

    var post1 = context.Posts.Find(1);
    Console.WriteLine(
        "State of post 1: {0} is {1}",
        post1.Name,  
        context.Entry(post1).State);  

    // Query some more posts from the database
    context.Posts.Where(p => p.Tags.Contains("asp.net")).Load();  

    // Loop over the posts in the context again.
    Console.WriteLine("\nIn Local after asp.net query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }
}

假設我們有一些貼文加上 'entity-framework' 和 'asp.net' 標記,輸出看起來可能像這樣:

In Local after entity-framework query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
State of post 1: EF Beginners Guide is Deleted

In Local after asp.net query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
Found 4: ASP.NET Beginners Guide with state Unchanged

這說明三點:

  • 新增至 Local 集合的新文章「EF 的新功能」,會由 [新增] 狀態的內容追蹤。 因此,呼叫 SaveChanges 時,它會插入資料庫中。
  • 已從本機集合中移除的貼文 (EF 初學者指南) 現在會在內容中標示為已刪除。 因此,呼叫 SaveChanges 時,將會從資料庫刪除它。
  • 載入內容的其他文章(ASP.NET 初學者指南),第二個查詢會自動新增至 Local 集合。

關於 Local 的最後一件事是,因為它是 ObservableCollection 效能對大量實體來說並不大。 因此,如果您要在內容中處理數千個實體,則不建議使用 Local。

針對 WPF 資料系結使用 Local

DbSet 上的 Local 屬性可以直接用於 WPF 應用程式中的資料系結,因為它是 ObservableCollection 的實例。 如前幾節所述,這表示它會自動與內容的內容保持同步,內容的內容會自動保持同步。 請注意,您確實需要預先填入 Local 集合的資料,因為 Local 永遠不會造成資料庫查詢,因此必須系結至任何專案。

這不是完整 WPF 資料系結範例的適當位置,但主要元素如下:

  • 設定系結來源
  • 將它系結至集合的 Local 屬性
  • 使用資料庫的查詢填入 Local。

WPF 系結至導覽屬性

如果您要執行主要/詳細資料系結,您可能想要將詳細資料檢視系結至其中一個實體的導覽屬性。 若要讓這項工作變得簡單,就是使用 ObservableCollection 作為導覽屬性。 例如:

public class Blog
{
    private readonly ObservableCollection<Post> _posts =
        new ObservableCollection<Post>();

    public int BlogId { get; set; }
    public string Name { get; set; }

    public virtual ObservableCollection<Post> Posts
    {
        get { return _posts; }
    }
}

使用 Local 清除 SaveChanges 中的實體

在大部分情況下,從導覽屬性移除的實體將不會在內容中自動標示為已刪除。 例如,如果您從 Blog.Post 集合中移除 Post 物件,則在呼叫 SaveChanges 時,將不會自動刪除該文章。 如果您需要刪除它,您可能需要尋找這些懸空的實體,並將其標示為已刪除,再呼叫 SaveChanges 或作為覆寫 SaveChanges 的一部分。 例如:

public override int SaveChanges()
{
    foreach (var post in this.Posts.Local.ToList())
    {
        if (post.Blog == null)
        {
            this.Posts.Remove(post);
        }
    }

    return base.SaveChanges();
}

上述程式碼會使用 Local 集合來尋找所有文章,並將任何沒有部落格參考的貼文標示為已刪除。 需要 ToList 呼叫,因為否則會由 Remove 呼叫在列舉時修改集合。 在大部分的其他情況下,您可以直接查詢 Local 屬性,而不先使用 ToList。

針對 Windows Forms 資料系結使用 Local 和 ToBindingList

Windows Forms 不支援直接使用 ObservableCollection 的完整逼真度資料系結。 不過,您仍然可以使用 DbSet Local 屬性進行資料系結,以取得上一節中所述的所有優點。 這是透過 ToBindingList 擴充方法達成,此方法會建立 由 Local ObservableCollection 支援的 IBindingList 實作。

這不是完整 Windows Forms 資料系結範例的適當位置,但主要元素如下:

  • 設定物件系結來源
  • 使用 Local.ToBindingList 將其系結至集合的 Local 屬性()
  • 使用查詢填入本機資料庫

取得已追蹤實體的詳細資訊

此系列中的許多範例都使用 Entry 方法傳回實體的 DbEntityEntry 實例。 然後,這個進入物件會做為收集實體相關資訊的起點,例如其目前狀態,以及在實體上執行作業,例如明確載入相關實體。

Entries 方法會針對內容所追蹤的許多或所有實體,傳回 DbEntityEntry 物件。 這可讓您收集資訊或對許多實體執行作業,而不只是單一專案。 例如:

using (var context = new BloggingContext())
{
    // Load some entities into the context
    context.Blogs.Load();
    context.Authors.Load();
    context.Readers.Load();

    // Make some changes
    context.Blogs.Find(1).Title = "The New ADO.NET Blog";
    context.Blogs.Remove(context.Blogs.Find(2));
    context.Authors.Add(new Author { Name = "Jane Doe" });
    context.Readers.Find(1).Username = "johndoe1987";

    // Look at the state of all entities in the context
    Console.WriteLine("All tracked entities: ");
    foreach (var entry in context.ChangeTracker.Entries())
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Find modified entities of any type
    Console.WriteLine("\nAll modified entities: ");
    foreach (var entry in context.ChangeTracker.Entries()
                              .Where(e => e.State == EntityState.Modified))
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Get some information about just the tracked blogs
    Console.WriteLine("\nTracked blogs: ");
    foreach (var entry in context.ChangeTracker.Entries<Blog>())
    {
        Console.WriteLine(
            "Found Blog {0}: {1} with original Name {2}",
            entry.Entity.BlogId,  
            entry.Entity.Name,
            entry.Property(p => p.Name).OriginalValue);
    }

    // Find all people (author or reader)
    Console.WriteLine("\nPeople: ");
    foreach (var entry in context.ChangeTracker.Entries<IPerson>())
    {
        Console.WriteLine("Found Person {0}", entry.Entity.Name);
    }
}

您會發現我們將 Author 和 Reader 類別引入範例中, 這兩個類別都會實作 IPerson 介面。

public class Author : IPerson
{
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public string Biography { get; set; }
}

public class Reader : IPerson
{
    public int ReaderId { get; set; }
    public string Name { get; set; }
    public string Username { get; set; }
}

public interface IPerson
{
    string Name { get; }
}

假設我們在資料庫中有下列資料:

BlogId = 1 且 Name = 'ADO.NET Blog'
BlogId = 2 且名稱 = 'The Visual Studio Blog'
BlogId = 3 且名稱 = '.NET Framework 部落格'
AuthorId = 1 且 Name = 'Joe Bloggs' 的作者
ReaderId = 1 且 Name = 'John Doe' 的讀取器

執行程式碼的輸出會是:

All tracked entities:
Found entity of type Blog with state Modified
Found entity of type Blog with state Deleted
Found entity of type Blog with state Unchanged
Found entity of type Author with state Unchanged
Found entity of type Author with state Added
Found entity of type Reader with state Modified

All modified entities:
Found entity of type Blog with state Modified
Found entity of type Reader with state Modified

Tracked blogs:
Found Blog 1: The New ADO.NET Blog with original Name ADO.NET Blog
Found Blog 2: The Visual Studio Blog with original Name The Visual Studio Blog
Found Blog 3: .NET Framework Blog with original Name .NET Framework Blog

People:
Found Person John Doe
Found Person Joe Bloggs
Found Person Jane Doe

這些範例說明幾個點:

  • Entries 方法會傳回所有狀態中實體的專案,包括 Deleted。 將此與排除已刪除實體的 Local 進行比較。
  • 使用非泛型 Entries 方法時,會傳回所有實體類型的專案。 使用泛型專案方法時,只會針對屬於泛型型別實例的實體傳回專案。 以上是用來取得所有部落格的專案。 它也用來取得實作 IPerson 之所有實體的專案。 這示範泛型型別不一定是實際的實體類型。
  • LINQ to Objects 可用來篩選傳回的結果。 這在上面用來尋找任何類型的實體,只要修改它們。

請注意,DbEntityEntry 實例一律包含非 Null 實體。 關聯性專案和存根專案不會表示為 DbEntityEntry 實例,因此不需要篩選這些專案。