在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
章节出自《Professional C# 6 and .NET Core 1.0》。水平有限,各位阅读时仔细分辨,唯望莫误人子弟。 附英文版原文:Professional C# 6 and .NET Core 1.0 - 38 Entity Framework Core 本章节译文分为上下篇,下篇见: C# 6 与 .NET Core 1.0 高级编程 - 38 章 实体框架核心(下) ------------------------------- 本章内容
Wrox.Com关于本章的源代码下载 本章的wrox.com代码下载位于 www.wrox.com/go/professionalcsharp6 下载代码选项卡。本章的代码主要有以下示例:
实体框架的历史
实体框架是提供实体到关系的映射的框架。通过这种方式,可以创建映射到数据库表的类型,使用LINQ创建数据库查询,创建和更新对象,并将它们写入数据库。 经过多年对Entity Framework的少量修改,最新的版本是一个完全的重写。一起来看看Entity Framework的历史,以及重写的原因。
随着更新(例如Entity Framework 4.1,4.2),NuGet包增加了额外的功能,因此能更快地添加功能。 Entity Framework 4.1提供了Code First模型,其中用于定义映射的EDMX文件不再使用。相反,所有的映射都使用C#代码定义 - 使用属性或Fluent API来定义的映射。 Entity Framework 4.3增加了对迁移的支持。有了这一点,就可以使用C#代码定义数据库结构的更改。使用数据库从应用程序自动应用数据库更新。
本书讨论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: |
请发表评论