本文章是由機器翻譯。

Entity Framework

ADO.NET Entity Framework 4.1 中的 Code First

Rowan Miller

ADO。NET 實體架構 4.1 回年 4 月於發行,並包含一系列的新功能,可以建立既有的實體架構 4 功能的 microsoft 發行的上方。NET 架構 4,Visual Studio 2010。

實體架構 4.1 是可用的獨立安裝程式 (msdn.microsoft.com/data/ee712906)、 為"entityframework"NuGet 封裝,並且當您安裝 ASP,將也包含。NET MVC 3.01。

實體架構 4.1 包含兩個新的主要功能:DbContext API 和程式碼第一次。 在本文中我將討論如何使用這兩個功能來開發應用程式。 我們將快速看一下開始使用第一個程式碼,並再深入的一些更進階的功能。

DbContext API 是簡化的抽象概念上現有的 ObjectContext 型別和被包含在先前的版本,Entity Framework 的其他類型的數字。 DbContext API 介面最適合的一般工作和程式碼撰寫的模式。 一般功能公開根層級和更進階的功能可以使用向下切入透過 API。

程式碼首先是 Entity Framework 所提供的現有資料庫第一和第一個模型模式的替代方案的新開發圖樣。 程式碼第一次可讓您定義模型使用 CLR 類別; 然後,您可以將這些類別對應至現有的資料庫,或使用它們來產生資料庫結構描述。 可以使用資料註解提供額外的設定,或是透過專有的 API。

快速入門

程式碼先已經周圍的一段時間,所以我不會對開始進入詳細資料。 您可以完成程式碼的第一個逐步解說 (bit.ly/evXlOc) 如果您不熟悉基本原則。 圖 1是完整的程式碼清單以協助您啟動且正在執行的程式碼第一個應用程式。

圖 1第一次開始使用程式碼

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System;

namespace Blogging
{
  class Program
  {
    static void Main(string[] args)
    {
      Database.SetInitializer<BlogContext>(new BlogInitializer());

      // TODO: Make this program do something!
}
  }

  public class BlogContext : DbContext
  {
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
      // TODO: Perform any fluent API configuration here!
}
  }

  public class Blog
  {
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Abstract { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
  }

  public class RssEnabledBlog : Blog
  {
    public string RssFeed { get; set; }
  }

  public class Post
  {
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public byte[] Photo { get; set; }

    public virtual Blog Blog { get; set; }
  }

  public class BlogInitializer : DropCreateDatabaseIfModelChanges<BlogContext>
  {
    protected override void Seed(BlogContext context)
    {
      context.Blogs.Add(new RssEnabledBlog
      {
        Name = "blogs.msdn.com/data",
        RssFeed = "http://blogs.msdn.com/b/data/rss.aspx",
        Posts = new List<Post>
        {
          new Post { Title = "Introducing EF4.1" },
          new Post { Title = "Code First with EF4.1" },
        }
      });

      context.Blogs.Add(new Blog { Name = "romiller.com" });
      context.SaveChanges();
    }
  }
}

為了簡單起見,為了的選擇,讓程式碼第產生資料庫。 資料庫將會建立第一次我使用 BlogContext 來保存和查詢的資料。 本文的其餘部分會同樣套用到其中第一個程式碼會對應至現有的資料庫結構描述的情況下。 您會注意到我在卸除並重新建立資料庫,當我們變更整篇文章模型使用在資料庫初始設定式。

具有專有 API 對應

程式碼先開始藉由檢查 CLR 類別,藉此推斷模型的形狀。 一系列的慣例用來偵測一類的主索引鍵資訊。 您可以覆寫,或新增到什麼偵測到依慣例使用資料註解] 或 [專有的 API。 有一些有關達到使用專有的 API,因此我將探討在可以執行更進階設定的一般工作的文件。 特別的是,我將著重在 API 中的 「 圖 」 章節。 對應組態可以用於將對應至現有的資料庫結構描述,或會影響產生的結構描述的形狀。 專有 API 公開透過 DbModelBuilder 型別,而最容易藉由覆寫上 DbContext 的 OnModelCreating 方法存取。

實體分割實體分割可讓被分散到多個資料表的實體型別的屬性。 比方說,假設我想要分割成不同的資料表出張貼的相片資料,因此這些元件可以儲存在不同的檔案群組中。 實體分割可用於多個對應呼叫對應至特定資料表的屬性的子集。 在圖 2,我正在將相片屬性對應到"PostPhotos"表格與其餘的屬性,以 「 回傳 」 資料表。 您會注意到我不包含主索引鍵中的屬性清單。 主索引鍵必須在每個資料表中。 我可以有包含,但第一個程式碼會自動將它加入在 [為我。

圖 2實體分割

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Post>()
    .Map(m =>
      {
        m.Properties(p => new { p.Title, p.Content });
        m.ToTable("Posts");
      })
    .Map(m =>
      {
        m.Properties(p => new { p.Photo });
        m.ToTable("PostPhotos");
      });
}

每個階層資料表 (TPH) 繼承TPH 牽涉到將資料儲存在單一資料表繼承階層架構,以及使用鑑別子資料行來識別每一列的型別。 程式碼首先會使用 TPH 預設情況下如果沒有設定提供。 鑑別子資料行就要適當命名"鑑別器 」,每一種類型的 CLR 型別名稱會用於鑑別子值。

您可能,不過,要自訂如何執行 TPH 對應。 若要執行這項操作,您可以使用的對應方法設定基底型別,然後對應 <TEntityType> 的鑑別子資料行值 若要設定每一個衍生型別。 我在此處使用"hasrssfeed"資料行來儲存 「 部落格"和"rssenabledblog"的執行個體之間加以區別的真/假值:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map(m => m.Requires("HasRssFeed").HasValue(false))
    .Map<RssEnabledBlog>(m => m.Requires("HasRssFeed").HasValue(true));
}

在先前範例中,我仍在使用獨立資料行來區別型別,但我知道 RssEnabledBlogs 可以由它們都擁有 RSS 餵送的事實。 我可以重寫,讓實體架構知道該網域控制站應該使用儲存"Blog.RssFeed"來區別型別之間的資料行對應。 如果該資料欄有非 null 值,該值必須是 RssEnabledBlog:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map<RssEnabledBlog>(m => m.Requires(b => b.RssFeed).HasValue());
}

每個類型的資料表 (TPT) 繼承TPT 牽涉到在單一資料表中儲存所有的屬性,與基底型別。 任何衍生型別之其他屬性則儲存在個別資料表的基底資料表的外部索引鍵上一步。 TPT 對應使用對應的呼叫來指定基底資料表名稱,然後對應 <TEntityType> 若要設定資料表的每一個衍生型別。 在下列範例中,我正在儲存通用於所有的部落格 」 部落格 」 資料表中的資料和特定 RSS 啟用部落格],"rssblogs"資料表中的資料:

modelBuilder.Entity<Blog>()
  .Map(m => m.ToTable("Blogs"))
  .Map<RssEnabledBlog>(m => m.ToTable("RssBlogs"));

資料表每個具象型別 (tpc 一起存檔) 繼承tpc 一起存檔牽涉到將每一種類型的資料儲存在它們之間沒有外部索引鍵條件約束以完全不同的資料表。 除了設定每一個衍生型別時,包含 「 MapInheritedProperties 」 呼叫,就像 TPT 對應組態。 MapInheritedProperties 可讓程式碼第知道要重新對應繼承自基底類別,衍生類別的資料表中的新資料行的所有屬性:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map(m => m.ToTable("Blogs"))
    .Map<RssEnabledBlog>(m =>
      {
        m.MapInheritedProperties();
        m.ToTable("RssBlogs");
      });
}

依照慣例,第一個程式碼會使用識別資料行的整數主索引鍵。 不過,與 tpc 一起存檔不再包含可以用來產生主索引鍵的所有部落格的單一資料表。 有鑑於此,第一個程式碼會關閉識別當您使用 tpc 一起存檔對應。 如果您要對應至現有的資料庫已設定多個資料表之間產生唯一值,您可以重新啟用透過專有 API 的屬性組態區段的識別。

交互式對應不用多說,您的結構描述的形狀不一直符合其中一個模式,我們已討論了,特別是當您對應至現有的資料庫。 好消息是對應的 API 可撰寫,而且您可以合併多個對應策略。 圖 3包含的範例會顯示組合實體分割與 TPT 繼承對應。 部落格的資料分割"部落格"和"blogabstracts"資料表之間,並啟用 RSS 的部落格的特定資料儲存在不同的"rssblogs"資料表。

圖 3結合實體分割 TPT 繼承對應

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Blog>()
    .Map(m =>
      {
        m.Properties(b => new { b.Name });
        m.ToTable("Blogs");
      })
    .Map(m =>
      {
        m.Properties(b => new { b.Abstract });
        m.ToTable("BlogAbstracts");
      })
    .Map<RssEnabledBlog>(m =>
      {
         m.ToTable("RssBlogs");
      });
}

變更追蹤程式 API

既然我已看設定資料庫對應,我想要花點時間使用的資料。 我要直接深入一些更進階的案例。 如果您不熟悉基本的資料存取,請花幾分鐘讀完稍早提到的第一個逐步解說程式碼。

狀態資訊的單一實體在許多情況下,例如記錄時,很有用的實體存取權的狀態資訊。 這可以包含一類的實體和哪一個屬性會被修改的狀態資訊。 DbContext 可存取此資訊可透過 [輸入] 方法的個別實體。 程式碼片段中的圖 4會從資料庫載入一個網誌、 修改屬性,然後再列印出至主控台的每個屬性的目前和原始值。

圖 4取得的實體狀態資訊

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Change the name of one blog
    var blog = db.Blogs.First();
    blog.Name = "ADO.NET Team Blog";

    // Print out original and current value for each property
    var propertyNames = db.Entry(blog).CurrentValues.PropertyNames;
    foreach (var property in propertyNames)
    {
      System.Console.WriteLine(
        "{0}\n Original Value: {1}\n Current Value: {2}", 
        property, 
        db.Entry(blog).OriginalValues[property],
        db.Entry(blog).CurrentValues[property]);
    }
  }

  Console.ReadKey();
}

當中的程式碼圖 4執行主控台輸出如下:

BlogId
原始值:1
目前的值:1
 
名稱
原始值:blogs.msdn.com/data
目前的值:ADO。NET 團隊部落格
 
抽象
原始值:
目前的值:
 
RssFeed
原始值:http://blogs.msdn.com/b/data/rss.aspx
目前的值:http://blogs.msdn.com/b/data/rss.aspx

狀態資訊的多個項目DbContext 可讓您存取有關透過"ChangeTracker.Entries"方法的多個項目資訊。 沒有提供特定類型的實體的泛型多載和非泛型多載,以提供所有實體。 泛用參數不需要實體型別。 例如,您無法將項目取得實作特定介面的所有已載入物件。 中的程式碼圖 5示範載入記憶體中的所有部落格、 修改其中一個圖形上的屬性以及然後列印出每個追蹤的部落格的狀態。

圖 5存取的多個項目與 DbContext 資訊

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load all blogs into memory
    db.Blogs.Load();

    // Change the name of one blog
    var blog = db.Blogs.First();
    blog.Name = "ADO.NET Team Blog";

    // Print out state for each blog that is in memory
    foreach (var entry in db.ChangeTracker.Entries<Blog>())
    {
      Console.WriteLine("BlogId: {0}\n State: {1}\n",
        entry.Entity.BlogId,
        entry.State);
    }
  }

當中的程式碼圖 5執行主控台輸出如下:

BlogId:1
狀態:修改
 
BlogId:2
狀態:不變

查詢本機執行個體查詢每當您對 DbSet 執行 LINQ 查詢,會處理傳送至資料庫。 這可以確保您永遠取得完整且最新的結果,但如果您知道您需要的所有資料都都已經在記憶體中,您可以查詢區域的資料來避免連接至資料庫。 中的程式碼圖 6載入至記憶體,所有的部落格,然後執行兩個 LINQ 查詢是否有任何網誌是不叫用資料庫。

圖 6執行 LINQ 查詢記憶體中的資料

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load all blogs into memory
    db.Blogs.Load();

    // Query for blogs ordered by name
    var orderedBlogs = from b in db.Blogs.Local 
                       orderby b.Name
                       select b;

    Console.WriteLine("All Blogs:");
    foreach (var blog in orderedBlogs)
    {
      Console.WriteLine(" - {0}", blog.Name);
    }

    // Query for all RSS enabled blogs
    var rssBlogs = from b in db.Blogs.Local
                   where b is RssEnabledBlog
                   select b;

    Console.WriteLine("\n Rss Blog Count: {0}", rssBlogs.Count());
  }

  Console.ReadKey();
}

當中的程式碼圖 6執行主控台輸出如下:

所有的部落格:
-blogs.msdn.com/data
-romiller.com
 
Rss 部落格計數: 1

導覽屬性為查詢DbContext 可讓您取得查詢,其代表指定的實體執行個體的導覽屬性的內容。 這可讓您以圖形或篩選的項目,您想要帶入記憶體,可以避免將帶回不必要的資料。

比方說,我有部落格的執行個體,且想要知道有多少文章。 我可以撰寫程式碼所示圖 7,但它藉由將所有相關的張貼回記憶體只如此我可以得知計數的延遲載入。

圖 7取得的延遲載入的資料庫項目個數

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load a single blog
    var blog = db.Blogs.First();

    // Print out the number of posts
    Console.WriteLine("Blog {0} has {1} posts.",
      blog.BlogId,
      blog.Posts.Count());
  }

  Console.ReadKey();
}

從資料庫傳輸的資料很多且佔用記憶體相較於單精確度整數結果我真的需要。

幸運的是,我可以藉由使用 DbContext 上的項目方法取得表示張貼部落格相關聯的集合的查詢最佳化執行我的程式碼。 因為 LINQ 是可撰寫、 我可以鏈結上的 「 計數 」 運算子,以便只的單精確度整數結果會傳回整個查詢取得放入資料庫 (請參閱圖 8)。

圖 8使用 DbContext 來最佳化查詢程式碼及儲存資源

static void Main(string[] args)
{
  Database.SetInitializer<BlogContext>(new BlogInitializer());

  using (var db = new BlogContext())
  {
    // Load a single blog
    var blog = db.Blogs.First();

    // Query for count
    var postCount = db.Entry(blog)
      .Collection(b => b.Posts)
      .Query()
      .Count();

    // Print out the number of posts
    Console.WriteLine("Blog {0} has {1} posts.",
      blog.BlogId,
      postCount);
  }

  Console.ReadKey();
}

部署考量

到目前為止我已看過如何取得啟動且正在執行與資料存取。 現在讓我們有點進一步外觀事先在您的應用程式需要考量的事項成熟,克灣修道院製作發行。

**連接字串:到目前為止我已經剛剛已讓程式碼第產生 localhost\SQLEXPRESS 的資料庫。**當問市時,若要部署應用程式時,可能想要變更程式碼第一個指在資料庫。 建議的作法是將連接字串項目加入至 App.config 檔案 (或 Web 應用程式的 Web.config)。 這也是建議使用這個方法使用第一個程式碼將對應至現有的資料庫。 如果連接字串名稱比對內容的完整型別名稱,第一個程式碼會自動挑選它在執行階段。 然而,建議的方法是使用 DbContext 建構函式可接受使用名稱 = < 連接字串名稱 > 的連線名稱 語法。 如此可確保第一個程式碼永遠會使用組態檔。 如果找不到連接字串項目,將會擲回例外狀況。 下列範例顯示可以用來對資料庫有影響的連接字串區段,我們範例應用程式之目標:

<connectionStrings>
  <add 
    name="Blogging" 
    providerName="System.Data.SqlClient"
    connectionString="Server=MyServer;Database=Blogging;
    Integrated Security=True;MultipleActiveResultSets=True;" />
</connectionStrings>

已更新的內容程式碼如下:

public class BlogContext : DbContext
{
  public BlogContext() 
    : base("name=Blogging")
  {}

  public DbSet<Blog> Blogs { get; set; }
  public DbSet<Post> Posts { get; set; }
}

請注意,建議啟用 「 多重作用結果集 」。 這可讓兩個查詢,以在同一時間會處於作用中。 比方說,這是需要列舉所有的部落格相關聯的部落格的柱子的查詢。

資料庫初始設定式根據預設,第一個程式碼會建立資料庫自動如果它為目標資料庫不存在。 對於某些各位,這會是所需的功能,甚至在部署時,並只是實際執行資料庫將會建立首次啟動應用程式。 如果您有 DBA 照顧實際執行環境,是更有可能 DBA 會建立生產資料庫,而且一旦部署應用程式如果它為目標資料庫不存在應該失敗。 在本文中我也已覆寫預設初始設定式邏輯,以及設定必須卸除然後重新建立每次我結構描述變更資料庫。 這絕對不是您想要使保留在原處,一旦您將部署到實際執行。

變更或部署時停用初始設定式行為的建議的作法是使用 App.config 檔案 (或 Web 應用程式的 Web.config)。 在 [和 appSettings] 區段中,加入的索引鍵是 DatabaseInitializerForType,後面加上內容型別名稱和定義它的組件的項目。 值可以是能 「 停用 」 或初始設定式型別名稱後面接著已定義的組件。

下面的範例抑止我已經在這份文件中所使用的內容任何初始設定式邏輯:

<appSettings>
  <add 
    key="DatabaseInitializerForType Blogging.BlogContext, Blogging" 
    value="Disabled" />
</appSettings>

下列範例將初始設定式回到它不存在時,才會建立資料庫的預設功能:

<appSettings>
  <add 
    key="DatabaseInitializerForType Blogging.BlogContext, Blogging" 
    value="System.Data.Entity.CreateDatabaseIfNotExists EntityFramework" />
</appSettings>

使用者帳戶如果您決定讓您建立資料庫的實際執行應用程式,應用程式將需要一開始執行 [使用具有建立資料庫及修改結構描述的權限的帳戶。如果這些權限都會保留下來,同樣會大幅增加應用程式的安全性危害的潛在的影響。我強烈建議應用程式執行最小的查詢及保存資料所需的權限集。

若要了解更多

我在本文中加總,花費快速地觀看開始使用第一個程式碼的開發和新的 DbContext API,兩者都是在 ADO 中的 included。NET 實體架構 4.1。您會看到如何使用專有的 API 以對應至現有的資料庫,或影響的資料庫結構描述所產生的程式碼第一個圖案。我再討論了變更追蹤程式 API,以及如何使用它來查詢本機實體執行個體和那些執行個體的其他資訊。最後,我討論如何使用程式碼第一個資料存取應用程式部署的考量因素。

如果您想要知道更多關於任何包含在實體架構 4.1 的功能,請造訪msdn.com/data/ef。您也可以使用資料開發人員中心論壇來取得使用實體架構 4.1 的說明:bit.ly/166o1Z

Rowan Miller 是一個程式管理員,Entity Framework 小組在 Microsoft。您可以深入了解 Entity Framework 上他的部落格,在romiller.com

感謝至下列技術專家檢閱這份文件:Arthur Vickers