C# 6 與 .NET Core 1.0 高級編程 - 38 章 實體框架核心(上) Professional C# 6 and .NET Core 1.0 - 38 Entity Framework

譯文,我的原創,轉載請註明出處(C# 6 與 .NET Core 1.0 高級編程 - 38 章 實體框架核心(上)),不對的地方歡迎指出與交流。 

章節出自《Professional C# 6 and .NET Core 1.0》。水平有限,各位閱讀時仔細分辨,惟望莫誤人子弟。html

附英文版原文:Professional C# 6 and .NET Core 1.0 - 38 Entity Framework Core數據庫

本章節譯文分爲上下篇,下篇見: C# 6 與 .NET Core 1.0 高級編程 - 38 章 實體框架核心(下)編程

-------------------------------json

本章內容框架

  • Entity Framework Core 1.0簡介
  • 使用依賴注入實體框架
  • 建立關係模型
  • 使用.NET CLI工具和MSBuild進行遷移
  • 對象跟蹤
  • 更新對象和對象樹
  • 衝突處理與更新
  • 使用事務

Wrox.Com關於本章的源代碼下載
async

本章的wrox.com代碼下載位於 www.wrox.com/go/professionalcsharp6 下載代碼選項卡。本章的代碼主要有如下示例:
編輯器

  • Books Sample
  • Books Sample with DI
  • Menus Sample
  • Menus with Data Annotations
  • Conflict Handling Sample
  • Transactions Sample 

實體框架的歷史

實體框架是提供實體到關係的映射的框架。經過這種方式,能夠建立映射到數據庫表的類型,使用LINQ建立數據庫查詢,建立和更新對象,並將它們寫入數據庫。 ide

通過多年對Entity Framework的少許修改,最新的版本是一個徹底的重寫。一塊兒來看看Entity Framework的歷史,以及重寫的緣由。函數

  • Entity Framework 1—Entity Framework的第一個版本沒有準備好與.NET 3.5兼容,但它很快就能夠與.NET 3.5 SP1兼容。另外一個產品LINQ to SQL提供了一些相似的功能,且已經可用於.NET 3.5。 LINQ to SQL和Entity Framework在很大程度上提供了相似的功能。LINQ to SQL更易於使用,但只能用於訪問SQL Server。實體框架是基於提供程序的,並提供了對不一樣關係數據庫的訪問。它包含更多的功能,例如多對多映射而不須要映射對象,n對n映射是可能的。 Entity Framework的一個缺點是它的模型類型須要由EntityObject基類派生。將對象映射到關係使用包含XML的EDMX文件完成的。包含的XML由三個模式組成:概念模式定義(CSD)定義具備其屬性和關聯的對象類型;存儲模式定義(SSD)定義數據庫表、列和關係;以及映射模式語言(MSL)定義CSD和SSD如何相互映射。
  • Entity Framework 4—Entity Framework 4 在.NET 4中兼容,而且得到了重大改進,其中許多來自LINQ到SQL的想法。因爲變化較大,版本2和3已被跳過。這個版本里增長了延遲加載以獲取訪問屬性的關係。在使用SQL數據定義語言(DDL)設計模型以後,能夠建立數據庫。如今使用Entity Framework的兩個模型是Database First或Model First。也許最重要的特性是支持簡單對象類(POCO),所以再也不須要從基類EntityObject派生。

隨着更新(例如Entity Framework 4.1,4.2),NuGet包增長了額外的功能,所以能更快地添加功能。 Entity Framework 4.1提供了Code First模型,其中用於定義映射的EDMX文件再也不使用。相反,全部的映射都使用C#代碼定義 - 使用屬性或Fluent API來定義的映射。工具

Entity Framework 4.3增長了對遷移的支持。有了這一點,就可使用C#代碼定義數據庫結構的更改。使用數據庫從應用程序自動應用數據庫更新。

  • Entity Framework 5—Entity Framework 5的NuGet包支持.NET 4.5和.NET 4應用程序。可是,Entity Framework 5的許多功能均可用於.NET 4.5。 Entity Framework仍然基於.NET 4.5在系統上安裝的類型。此版本的新增功能是性能改進以及支持新的SQL Server功能,例如空間數據類型。
  • Entity Framework 6—Entity Framework 6解決了Entity Framework 5的一些問題,其中一部分是安裝在系統上的框架的一部分,一部分經過NuGet擴展提供。目前Entity Framework的所有代碼已移至NuGet包。爲了避免形成衝突,使用了一個新的命名空間。將應用程序移植到新版本時,必須更改命名空間。

本書討論Entity Framework的最新版本,Entity Framework Core 1.0。此版本是一個刪除舊的行爲全面重寫,再也不支持CSDL,SSDL和MSL的XML文件映射,只支持Code First - 使用Entity Framework 4.1添加的模型。Code First 並不意味着數據庫不能先存在。您能夠先建立數據庫,或者僅從代碼中定義數據庫,以上兩種選項都是可行的。

注意 Code First 這個名稱某些程度上讓人誤會。Code First 先建立代碼或先數據庫都是可行的。最初Code First的測試版本名稱是Code Only。由於其餘模型選項在名稱中有First,因此「Code Only」的名稱也被更改。

Entity Framework 的全面重寫不只支持關係數據庫,還支持NoSql數據庫 - 只須要一個提供程序。在撰寫本文時,提供程序支持有限,但相信會隨時間而增長。 

新版本的Entity Framework基於.NET Core,所以在Linux和Mac系統上也可使用此框架。 

Entity Framework Core 1.0不徹底支持Entity Framework 6提供的全部功能。隨着時間的推移,Entity Framework的新版本將提供更多功能,留意所使用的Entity Framework的版本。儘管使用Entity Framework 6 不少有力的理由,但在非Windows平臺上使用ASP.NET Core 1.0、Entity Framework和通用Windows平臺(UWP),以及非關係數據存儲,都須要使用Entity Framework Core 1.0。 

本章介紹Entity Framework Core 1.0。從一個簡單的模型讀取和SQL Server中寫入信息開始,稍後會介紹添加關係,在寫入數據庫時將介紹更改跟蹤器和衝突處理。利用遷移建立和修改數據庫結構是本章的另外一個重要部分。 

注意 本章使用Books數據庫,此數據庫包含在示例代碼的下載包中 www.wrox.com/go/professionalcsharp6

實體框架簡介

第一個示例使用單個Book類型,並將此類型映射到SQL Server數據庫中的Books表。能夠將記錄寫入數據庫,而後讀取,更新和刪除它們。 

在第一個示例中,首先建立數據庫。可使用Visual Studio 2015中的SQL Server對象資源管理器執行此操做。選擇數據庫實例(與Visual Studio一塊兒安裝的(localdb)\ MSSQLLocalDB),單擊樹視圖中的數據庫節點,而後選擇「添加新數據庫」。示例數據庫只有一個名爲Books的表。 

選擇Books數據庫中的表節點,而後選擇"添加新表"來建立表Books。使用圖38.1中所示的設計器,或者經過在T-SQL編輯器中輸入SQL DDL語句,均可以建立表Books。如下代碼段顯示了用於建立表的T-SQL代碼。單擊「更新」按鈕能夠將更改提交到數據庫。

CREATE TABLE [dbo].[Books]
(
  [BookId] INT NOT NULL PRIMARY KEY IDENTITY,
  [Title] NVARCHAR(50) NOT NULL,
  [Publisher] NVARCHAR(25) NOT NULL
)

建立模型 

用於訪問Books數據庫的示例應用程序BookSample是一個控制檯應用程序(Package)。此示例使用如下依賴項和命名空間:

  依賴項

NETStandard.Library
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer

   命名空間

Microsoft.EntityFrameworkCore
System.ComponentModel.DataAnnotations.Schema
System
System.Linq
System.Threading.Tasks
static System.Console

 

圖 38.1  

Book類是一個簡單的實體類型,它定義了三個屬性。 BookId屬性映射到表的主鍵,Title屬性指向標題列,Publisher屬性指向Publisher列。Table屬性應用於類型將類型映射到Books表(代碼文件BooksSample / Book.cs):

[Table("Books")]
public class Book
{
  public int BookId { get; set; }
  public string Title { get; set; }
  public string Publisher { get; set; }
}

建立上下文 

建立的BooksContext類完成Book表與數據庫的關聯。這個類派生自基類DbContext,BooksContext類定義Books屬性類型爲DbSet <Book>。此類型容許建立查詢並添加Book實例以將其存儲在數據庫中。要定義鏈接字符串,能夠重寫DbContext的OnConfiguring方法。UseSqlServer擴展方法將上下文映射到SQL Server數據庫(代碼文件BooksSample / BooksContext.cs):

public class BooksContext: DbContext
{
  private const string ConnectionString =  @"server= (localdb)\MSSQLLocalDb;database=Books;trusted_connection=true";
  public DbSet<Book> Books { get; set; }
  protected override void OnConfiguring(DbContextOptionsBuilder  optionsBuilder)
  {
    base.OnConfiguring(optionsBuilder);
    optionsBuilder.UseSqlServer(ConnectionString);
  }
}

定義鏈接字符串的另外一個選項是使用依賴注入,將在本章後面介紹。 

寫入數據庫

如今已建立了有Books表的數據庫,也定義了模型和上下文類,而後能夠用數據填充表。建立AddBookAsync方法將Book對象添加到數據庫。首先,BooksContext對象被實例化,這裏使用using語句確保數據庫鏈接關閉。使用Add方法將對象添加到上下文以後,實體被寫入調用SaveChangesAsync的數據庫(代碼文件BooksSample / Program.cs):

private async Task AddBookAsync(string title, string publisher)
{
  using (var context = new BooksContext())
  {
    var book = new Book
    {
      Title = title,
      Publisher = publisher
    };
    context.Add(book);
    int records = await context.SaveChangesAsync();
 
    WriteLine($"{records} record added");
  }
  WriteLine();
} 

要添加書籍列表,可使用AddRange方法(代碼文件BooksSample / Program.cs):

private async Task AddBooksAsync()
{
  using (var context = new BooksContext())
  {
    var b1 = new Book
    {
      Title ="Professional C# 5 and .NET 4.5.1",
      Publisher ="Wrox Press"
    };
    var b2 = new Book
    {
      Title ="Professional C# 2012 and .NET 4.5",
      Publisher ="Wrox Press"
    };
    var b3 = new Book
    {
      Title ="JavaScript for Kids",
      Publisher ="Wrox Press"
    };
    var b4 = new Book
    {
      Title ="Web Design with HTML and CSS",
      Publisher ="For Dummies"
    };
    context.AddRange(b1, b2, b3, b4);
    int records = await context.SaveChangesAsync();
    WriteLine($"{records} records added");
  }
  WriteLine();
} 

 運行應用程序並調用這些方法後,可使用SQL Server對象資源管理器查看寫入到數據庫的數據。

從數據庫讀取

從C#代碼讀取數據只須要調用BooksContext並訪問Books屬性。訪問此屬性會建立一個SQL語句從數據庫中檢索全部圖書(代碼文件BooksSample / Program.cs):

private void ReadBooks()
{
  using (var context = new BooksContext())
  {
    var books = context.Books;
    foreach (var b in books)
    {
      WriteLine($"{b.Title} {b.Publisher}");
    }
  }
  WriteLine();
}

在調試期間打開 IntelliTrace Events窗口,能夠看到發送到數據庫的SQL語句(須要Visual Studio 企業版):

SELECT [b].[BookId], [b].[Publisher], [b].[Title]
FROM [Books] AS [b]

Framework提供了一個LINQ提供程序,能夠建立LINQ查詢訪問數據庫。可使用以下所示語法的方法:

private void QueryBooks()
{
  using (var context = new BooksContext())
  {
    var wroxBooks = context.Books.Where(b => b.Publisher =="Wrox Press");
    foreach (var b in wroxBooks)
    {
      WriteLine($"{b.Title} {b.Publisher}");
    }
  }
  WriteLine();
}

或使用LINQ查詢語法:

var wroxBooks = from b in context.Books
                where b.Publisher =="Wrox Press"
                select b;

使用這兩種不一樣的語法,都將發送下面的SQL語句到數據庫:

SELECT [b].[BookId], [b].[Publisher], [b].[Title]
FROM [Books] AS [b]
WHERE [b].[Publisher] = 'Wrox Press'

注意 在第13章「語言集成查詢」中詳細討論了LINQ。

更新記錄

只需更改已加載上下文的對象並調用SaveChangesAsync便可輕鬆實現更新記錄(代碼文件BooksSample / Program.cs):

private async Task UpdateBookAsync()
{
  using (var context = new BooksContext())
  {
    int records = 0;
    var book = context.Books.Where(b => b.Title =="Professional C# 6")
      .FirstOrDefault();
    if (book != null)
    {
      book.Title ="Professional C# 6 and .NET Core 5";
      records = await context.SaveChangesAsync();
    }
    WriteLine($"{records} record updated");
  }
  WriteLine();
}

刪除記錄

最後,讓咱們清理數據庫並刪除全部記錄。能夠經過檢索全部記錄並調用Remove或RemoveRange方法來設置上下文中要刪除的對象的狀態。而後調用SaveChangesAsync方法便可從數據庫中刪除記錄,DbContext會爲每一個要刪除的對象調用SQL Delete語句(代碼文件BooksSample / Program.cs):

private async Task DeleteBooksAsync()
{
  using (var context = new BooksContext())
  {
    var books = context.Books;
    context.Books.RemoveRange(books);
    int records = await context.SaveChangesAsync();
    WriteLine($"{records} records deleted");
  }
  WriteLine();
}

注意 對象關係映射工具(如Entity Framework)在並不是在全部方案中均可用。使用示例代碼沒法有效地刪除全部對象。您可使用一個SQL語句刪除全部而不是逐條刪除記錄。在第37章「ADO.NET」中解釋了如何作到這一點

瞭解瞭如何添加、查詢、更新和刪除記錄,本章將介紹幕後的功能,並使用Entity Framework進入高級場景。

使用依賴注入  

Entity Framework Core 1.0內置了對依賴注入的支持。鏈接和SQL Server選擇能夠經過使用依賴注入框架注入,而非定義和而後使用DbContext派生類的SQL Server鏈接。 

要查看此操做,BooksSampleWithDI示例項目對上一個代碼示例項目進行了修改。 

此示例使用如下依賴項和命名空間:

  依賴項

NETStandard.Library
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.Framework.DependencyInjection 

  命名空間

Microsoft.EntityFrameworkCore
System.Linq
System.Threading.Tasks
static System.Console

BooksContext類如今看起來很簡單,只需定義Books屬性(代碼文件BooksSampleWithDI / BooksContext.cs):

public class BooksContext: DbContext
{
  public DbSet<Book> Books { get; set; }
}

BooksService是使用BooksContext的新類。BooksContext經過注入構造函數注入。方法AddBooksAsync和ReadBooks與上一個示例中的這些方法很是類似,但他們使用BooksService類的上下文成員,而不是建立一個新的(代碼文件BooksSampleWithDI / BooksService.cs):

public class BooksService
{
  private readonly BooksContext _booksContext;
  public BooksService(BooksContext context)
  {
    _booksContext = context;
  }
 
  public async Task AddBooksAsync()
  {
    var b1 = new Book
    {
      Title ="Professional C# 5 and .NET 4.5.1",
      Publisher ="Wrox Press"
    };
    var b2 = new Book
    {
      Title ="Professional C# 2012 and .NET 4.5",
      Publisher ="Wrox Press"
    };
    var b3 = new Book
    {
      Title ="JavaScript for Kids",
      Publisher ="Wrox Press"
    };
    var b4 = new Book
    {
      Title ="Web Design with HTML and CSS",
      Publisher ="For Dummies"
    };
    _booksContext.AddRange(b1, b2, b3, b4);
    int records = await _booksContext.SaveChangesAsync();
 
    WriteLine($"{records} records added");
  }
 
  public void ReadBooks()
  {
    var books = _booksContext.Books;
    foreach (var b in books)
    {
      WriteLine($"{b.Title} {b.Publisher}");
    }
    WriteLine();
  }
} 

依賴注入框架的容器在 InitializeServices 方法中初始化。建立一個ServiceCollection實例,將BooksService類添加到此集合中,並進行臨時生命週期管理。這樣,每次請求該服務時都會實例化 ServiceCollection。對於註冊Entity Framework和SQL Server,能夠用擴展方法AddEntityFramework,AddSqlServer和AddDbContext。 AddDbContext方法須要一個Action委託做爲參數,其中接收到一個DbContextOptionsBuilder參數。有了該選項參數,可使用UseSqlServer擴展方法配置上下文。這裏用Entity Framework註冊SQL Server與上一個示例是相似的功能(代碼文件BooksSampleWithDI / Program.cs):

private void InitializeServices()
{
  const string ConnectionString =@"server= (localdb)\MSSQLLocalDb;database=Books;trusted_connection=true";
  var services = new ServiceCollection();
  services.AddTransient<BooksService>();
  services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<BooksContext>(options =>
      options.UseSqlServer(ConnectionString));
  Container = services.BuildServiceProvider();
}
 
public IServiceProvider Container { get; private set; }

服務的初始化以及BooksService的使用是從Main方法完成的。經過調用IServiceProvider的GetService方法來檢索BooksService(代碼文件BooksSampleWithDI / Program.cs):

static void Main()
{
  var p = new Program();
  p.InitializeServices();
 
  var service = p.Container.GetService<BooksService>();
  service.AddBooksAsync().Wait();
  service.ReadBooks();
}

運行應用程序能夠看到記錄已添加到圖書數據庫中而後從中讀取記錄。

注意 在第31章「XAML應用程序的模式」中閱讀有關依賴注入和Microsoft.Framework.DependencyInjection包的更多信息,還能夠參見第40章「ASP.NET Core」和第41章「 ASP.NET MVC「。

建立模型  

本章的第一個示例映射單個表。第二個例子顯示了建立表之間的關係。在本節中使用C#代碼建立數據庫而沒有使用SQL DDL語句(或經過使用設計器)建立數據庫。 

示例應用程序MenusSample使用如下依賴項和命名空間:

  依賴項

NETStandard.Library
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer

  命名空間

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.ChangeTracking
System
System.Collections.Generic
System.ComponentModel.DataAnnotations
System.ComponentModel.DataAnnotations.Schema
System.Linq
System.Threading
System.Threading.Tasks
static System.Console

建立關係

讓咱們開始建立一個模型。示例項目使用MenuCard和Menu類型定義一對多關係。MenuCard包含Menu對象的列表。這種關係由List <Menu>類型的Menu屬性簡單定義(代碼文件MenusSample / MenuCard.cs):

public class MenuCard
{
  public int MenuCardId { get; set; }
  public string Title { get; set; }
  public List<Menu> Menus { get; } = new List<Menu>();
 
  public override string ToString() => Title;
}

該關係也能夠從另外一個角度訪問,菜單可使用MenuCard屬性訪問MenuCard。指定 MenuCardId 屬性去定義外鍵關係(代碼文件MenusSample / Menu.cs):

public class Menu
{
  public int MenuId { get; set; }
  public string Text { get; set; }
  public decimal Price { get; set; }
 
  public int MenuCardId { get; set; }
  public MenuCard MenuCard { get; set; }
 
  public override string ToString() => Text;
}

到數據庫的映射由MenusContext類完成。這個類定義爲與上一個上下文類型相似的類型,它只包含兩個屬性來映射兩個對象類型:屬性Menus和MenuCards(代碼文件MenusSamples / MenusContext.cs):

public class MenusContext: DbContext
{
  private const string ConnectionString = @"server=(localdb)\MSSQLLocalDb;" +     "Database=MenuCards;Trusted_Connection=True";
  public DbSet<Menu> Menus { get; set; }
  public DbSet<MenuCard> MenuCards { get; set; }
 
  protected override void OnConfiguring(DbContextOptionsBuilder  optionsBuilder)
  {
    base.OnConfiguring(optionsBuilder);
    optionsBuilder.UseSqlServer(ConnectionString);
  }
}

使用.NET CLI進行遷移

要使用C#代碼自動建立數據庫,可使用enet工具使用package dotnet-ef擴展.NET CLI工具。此軟件包包含用於爲遷移建立C#代碼的命令。經過安裝dotnet-ef NuGet包可使命令可用。您能夠經過從項目配置文件(代碼文件MenusSample / project.json)中的工具部分引用此軟件包來安裝它:

"tools": {
  "dotnet-ef":"1.0.0-*"
 }

ef命令提供如下命令:數據庫、dbcontext和遷移。數據庫命令用於將數據庫升級到特定的遷移狀態。 dbcontext命令列出項目中的全部DbContext派生類型(dbcontext list),並從數據庫(dbcontext scaffold)建立上下文和實體。 migrations命令則建立和刪除遷移,以及建立SQL腳本去建立包含全部遷移的數據庫。若是生產數據庫只能從SQL管理員使用SQL代碼建立和修改,能夠將生成的腳本移交給SQL管理員。 

爲了建立初始遷移以從代碼建立數據庫,能夠從開發人員命令提示符調用如下命令,該命令建立名爲InitMenuCards的遷移:

>dotnet ef migrations add InitMenuCards

命令migrations add使用反射以及相反的引用模型訪問DbContext派生類。此信息建立兩個類來建立和更新數據庫。使用Menu,MenuCard和MenusContext類建立兩個類,MenusContextModelSnapshot和InitMenuCards。命令成功後能夠在Migrations文件夾中找到這兩種類型。

MenusContextModelSnapshot類包含構建數據庫的模型的當前狀態:

[DbContext(typeof(MenusContext))] partial class MenusContextModelSnapshot: ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder .HasAnnotation("ProductVersion","7.0.0-rc1-16348") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("MenusSample.Menu", b => { b.Property<int>("MenuId") .ValueGeneratedOnAdd(); b.Property<int>("MenuCardId"); b.Property<decimal>("Price"); b.Property<string>("Text"); b.HasKey("MenuId"); }); modelBuilder.Entity("MenusSample.MenuCard", b => { b.Property<int>("MenuCardId") .ValueGeneratedOnAdd(); b.Property<string>("Title"); b.HasKey("MenuCardId"); }); modelBuilder.Entity("MenusSample.Menu", b => { b.HasOne("MenusSample.MenuCard") .WithMany() .HasForeignKey("MenuCardId"); }); } }

InitMenuCards類定義了Up和Down方法。 Up方法列出了建立MenuCard和菜單表所需的全部操做,包括主鍵、列和關係。 Down方法刪除兩個表:

public partial class InitMenuCards: Migration
{
  protected override void Up(MigrationBuilder migrationBuilder)
  {
    migrationBuilder.CreateTable(
      name:"MenuCard",
      columns: table => new
      {
        MenuCardId = table.Column<int>(nullable: false)
          .Annotation("SqlServer:ValueGenerationStrategy",
            SqlServerValueGenerationStrategy.IdentityColumn),
        Title = table.Column<string>(nullable: true)
      },
      constraints: table =>
      {
        table.PrimaryKey("PK_MenuCard", x => x.MenuCardId);
      });
 
    migrationBuilder.CreateTable(
      name:"Menu",
      columns: table => new
      {
        MenuId = table.Column<int>(nullable: false)
          .Annotation("SqlServer:ValueGenerationStrategy",
            SqlServerValueGenerationStrategy.IdentityColumn),
        MenuCardId = table.Column<int>(nullable: false),
        Price = table.Column<decimal>(nullable: false),
        Text = table.Column<string>(nullable: true)
      },
      constraints: table =>
      {
        table.PrimaryKey("PK_Menu", x => x.MenuId);
        table.ForeignKey(
          name:"FK_Menu_MenuCard_MenuCardId",
          column: x => x.MenuCardId,
          principalTable:"MenuCard",
          principalColumn:"MenuCardId",
          onDelete: ReferentialAction.Cascade);
      });
  }
 
  protected override void Down(MigrationBuilder migrationBuilder)
  {
    migrationBuilder.DropTable("Menu");
    migrationBuilder.DropTable("MenuCard");
  }
}

注意 正在進行的每一個更改均可以建立另外一個遷移。新遷移僅定義從先前版本到新版本所需的更改。若是客戶的數據庫須要從任意早期的版本更新,遷移數據庫時調用必要的遷移。 

在開發過程當中,也行不須要全部的遷移,可能須要從項目中建立,由於可能沒有該類臨時狀態的數據庫存在。在這種狀況下能夠刪除遷移並建立一個較大的新遷移。

使用MSBuild進行遷移  

若是您正在使用基於MSBuild的項目Entity Framework遷移而不是DNX,遷移命令是不一樣的。使用完整框架控制檯應用程序、WPF應用程序或ASP.NET 4.6項目類型,須要在NuGet包管理器控制檯中指定遷移命令,而不是開發人員命令提示符。從Visual Studio經過 工具➪庫管理器控制檯➪包管理器控制檯 啓動包管理器控制檯。

在包管理器控制檯可使用PowerShell腳本添加和刪除遷移。命令以下

> Add-Migration InitMenuCards

建立一個Migrations文件夾,其中包含如前所示的遷移類。

建立數據庫 

隨着遷移類型到位,能夠建立數據庫。 DbContext派生類MenusContext包含一個返回DatabaseFacade對象的Database屬性。使用DatabaseFacade能夠建立和刪除數據庫。若是數據庫不存在,EnsureCreated方法會建立數據庫;若是數據庫已存在,則不執行任何操做。方法EnsureDeletedAsync刪除數據庫。如下代碼片斷建立數據庫(若是它不存在)(代碼文件MenusSample / Program.cs):

private static async Task CreateDatabaseAsync()
{
  using (var context = new MenusContext())
  {
bool created = await context.Database.EnsureCreatedAsync();
    string createdText = created ?"created":"already exists";
    WriteLine($"database {createdText}");
  }
}

注意 若是數據庫存在可是一個較舊的結構版本,EnsureCreatedAsync方法不會應用結構更改。這時能夠經過調用Migrate方法來進行結構升級。 Migrate是Microsoft.Data.Entity命名空間中定義的DatabaseFacade類的擴展方法。

運行程序將建立表MenuCard和Menu。基於默認約定,表與實體類型是相同的名稱。另外一個約定用於建立主鍵:MenuCardId列會被定義爲主鍵,由於屬性名以Id結束。

CREATE TABLE [dbo].[MenuCard] (
  [MenuCardId] INT            IDENTITY (1, 1) NOT NULL,
  [Title]      NVARCHAR (MAX) NULL,
  CONSTRAINT [PK_MenuCard] PRIMARY KEY CLUSTERED ([MenuCardId] ASC)
);

Menu表定義了MenuCardId,它是MenuCard表的外鍵。因爲DELETE CASCADE,刪除MenuCard也會刪除全部關聯的Menu行:

CREATE TABLE [dbo].[Menu] (
  [MenuId]     INT             IDENTITY (1, 1) NOT NULL,
  [MenuCardId] INT             NOT NULL,
  [Price]      DECIMAL (18, 2) NOT NULL,
  [Text]       NVARCHAR (MAX)  NULL,
  CONSTRAINT [PK_Menu] PRIMARY KEY CLUSTERED ([MenuId] ASC),
  CONSTRAINT [FK_Menu_MenuCard_MenuCardId] FOREIGN KEY ([MenuCardId])
  REFERENCES [dbo].[MenuCard] ([MenuCardId]) ON DELETE CASCADE
);

在建立代碼中有一些部分改變是有用的。例如,Text 和 Title 列的大小能夠從NVARCHAR(MAX)減少,SQL Server定義了可用於Price列的Money類型,而且結構名稱能夠從dbo更改。 Entity Framework提供了兩個選項從代碼中執行這些更改:數據批註和Fluent API,下面將討論。

數據批註

影響生成的數據庫的一種方法是向實體類型添加數據註釋。能夠利用Table屬性更改表的名稱。要更改結構名稱,Table屬性定義Schema屬性。若是要爲字符串類型指定不一樣的長度,可使用MaxLength屬性(代碼文件MenusWithDataAnnotations / MenuCard.cs):

[Table("MenuCards", Schema ="mc")]
public class MenuCard
{
  public int MenuCardId { get; set; }
  [MaxLength(120)]
  public string Title { get; set; }
  public List<Menu> Menus { get; }
}

Menu類的Table和MaxLength屬性一樣能夠應用。使用Column屬性更改SQL類型(代碼文件MenusWithDataAnnotations / Menu.cs):

[Table("Menus", Schema ="mc")]
public class Menu
{
  public int MenuId { get; set; }
  [MaxLength(50)]
  public string Text { get; set; }
  [Column(TypeName ="Money")]
  public decimal Price { get; set; }
  public int MenuCardId { get; set; }
  public MenuCard MenuCard { get; set; }
}

應用遷移建立數據庫後能夠看到結構名稱下表的新名稱,以及Title、Text 和 Price 字段中已更改的數據類型:

CREATE TABLE [mc].[MenuCards] (
  [MenuCardId] INT            IDENTITY (1, 1) NOT NULL,
  [Title]      NVARCHAR (120) NULL,
  CONSTRAINT [PK_MenuCard] PRIMARY KEY CLUSTERED ([MenuCardId] ASC)
);
 
CREATE TABLE [mc].[Menus] (
  [MenuId]     INT           IDENTITY (1, 1) NOT NULL,
  [MenuCardId] INT           NOT NULL,
  [Price]      MONEY         NOT NULL,
  [Text]       NVARCHAR (50) NULL,
  CONSTRAINT [PK_Menu] PRIMARY KEY CLUSTERED ([MenuId] ASC),
  CONSTRAINT [FK_Menu_MenuCard_MenuCardId] FOREIGN KEY ([MenuCardId])
    REFERENCES [mc].[MenuCards] ([MenuCardId]) ON DELETE CASCADE
);

Fluent API  

影響建立的表的另外一種方法是使用Fluent API中DbContext派生類的OnModelCreating方法。它的優勢是能夠保持實體類型簡單,而不添加任何屬性,Fluent API還提供了比應用屬性更多的選項。 

如下代碼片斷顯示了BooksContext類重寫OnModelCreating方法。做爲參數接收的ModelBuilder類提供了一些方法,並定義了幾種擴展方法。 HasDefaultSchema是其中一個擴展方法,它將默認結構應用於目前全部類型的模型。 Entity方法返回一個EntityTypeBuilder,使您可以自定義實體,例如將其映射到特定的表名和定義鍵和索引(代碼文件MenusSample / MenusContext.cs):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  base.OnModelCreating(modelBuilder);
 
  modelBuilder.HasDefaultSchema("mc");
 
  modelBuilder.Entity<MenuCard>()
    .ToTable("MenuCards")
    .HasKey(c => c.MenuCardId);
 
  // etc.
 
  modelBuilder.Entity<Menu>()
    .ToTable("Menus")
    .HasKey(m => m.MenuId);
 
  // etc.
}

EntityTypeBuilder定義了一個Property方法來配置屬性。 Property方法返回PropertyBuilder,可以依次配置具備最大長度值,必要的設置和SQL類型的屬性,並指定是否應自動生成值(例如標識列):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  // etc.
 
  modelBuilder.Entity<MenuCard>()
    .Property<int>(c => c.MenuCardId)
    .ValueGeneratedOnAdd();
 
  modelBuilder.Entity<MenuCard>()
    .Property<string>(c => c.Title)
    .HasMaxLength(50);
 
  modelBuilder.Entity<Menu>()
    .Property<int>(m => m.MenuId)
    .ValueGeneratedOnAdd();
 
  modelBuilder.Entity<Menu>()
.Property<string>(m => m.Text)
    .HasMaxLength(120);
 
  modelBuilder.Entity<Menu>()
    .Property<decimal>(m => m.Price)
    .HasColumnType("Money");
 
  // etc.
} 

EntityTypeBuilder定義映射方法去定義一對多映射。HasMany 結合 WithOne 方法定義了多Menus 和一個Menu Card 的映射。 HasMany須要與WithOne連接,即HasOne方法須要一個帶WithMany或WithOne的鏈。連接 HasOne 和 WithMany定義了一對多關係,連接HasOne與WithOne定義了一對一的關係:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  // etc.
 
  modelBuilder.Entity<MenuCard>()
    .HasMany(c => c.Menus)
    .WithOne(m => m.MenuCard);
  modelBuilder.Entity<Menu>()
    .HasOne(m => m.MenuCard)
    .WithMany(c => c.Menus)
    .HasForeignKey(m => m.MenuCardId);
}

在OnModelCreating方法中建立映射以後能夠建立如前所示的遷移。

從數據庫建立模型  

從模型能夠建立數據庫,相反從數據庫也能夠建立模型。 

要從SQL Server數據庫執行此操做,除了其餘包,還必須將NuGet包添加到DNX項目中,EntityFramework.MicrosoftSqlServer.Design。而後能夠在開發人員命令提示符使用如下命令:

> dnx ef dbcontext scaffold 
"server=(localdb)\MSSQLLocalDb;database=SampleDatabase; trusted_connection=true""EntityFramework.MicrosoftSqlServer"

dbcontext命令可以從項目中列出DbContext對象,同時也建立DBContext對象。命令scaffold建立DbContext派生類以及模型類。 dnx ef dbcontext scaffold 須要兩個必要的參數:數據庫的鏈接字符串和使用的提供程序。前面所示的語句中,在SQL Server(localdb)\ MSSQLLocalDb上訪問數據庫SampleDatabase。使用的提供程序是EntityFramework.MicrosoftSqlServer。這個NuGet包以及具備相同名稱和設計後綴的NuGet包必須添加到項目中。 

運行此命令後,能夠看到DbContext派生類以及生成的模型類型。默認狀況下,模型的配置使用fluent API完成。可是也能夠將其更改成使用提供-a選項的數據批註。還能夠影響生成的上下文類名稱以及輸出目錄。只需使用選項-h檢查不一樣的可用選項。

 

----------------未完待續

相關文章
相關標籤/搜索