本篇介绍原书的第 18 章,为 19 章 Restful Service 编写基础代码。本章实现了如下内容:
1)使用 Entity Framework Core 操作 Sql Server 数据库
2)Entity Framework Core 数据库迁移和使用种子数据的方法
3)使用中间件 (middleware) 来配置请求管道 (request pipeline)
为了运行本章代码,需要下面的三个包:
Microsoft.EntityFrameworkCore.SqlServer --version 3.1.1
Microsoft.EntityFrameworkCore.Design --version 3.1.1
Microsoft.EntityFrameworkore.Tool --version 3.1.1
创建 Products, Category 和 Suppliers 三个实体模型:
namespace WebApp.Models
{
public class Category
{
public long CategoryId { get; set; }
public string Name { get; set; }
public IEnumerable<Product> Products { get; set; }
}
}
namespace WebApp.Models
{
public class Supplier
{
public long SupplierId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public IEnumerable<Product> Products { get; set; }
}
}
namespace WebApp.Models
{
public class Product
{
public long ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public long CategoryId { get; set; }
public Category Category { get; set; }
public long SupplierId { get; set; }
public Supplier Supplier { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
namespace WebApp.Models
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> opts) : base(opts) { }
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace WebApp.Models
{
public class SeedData
{
public static void SeedDatabase(DataContext context)
{
context.Database.Migrate();
// 只有在空的时候才填充数据
if (context.Products.Count() == 0 && context.Suppliers.Count() == 0 && context.Categories.Count() == 0) {
// 供应商
var s1 = new Supplier
{
Name = "Splash Dudes",
City = "San Jose"
};
var s2 = new Supplier
{
Name = "Soccer Town",
City = "Chicago"
};
var s3 = new Supplier
{
Name = "Chess Co",
City = "New York"
};
// Category
var c1 = new Category { Name = "Watersports" };
var c2 = new Category { Name = "Soccer" };
var c3 = new Category { Name = "Chess" };
context.Products.AddRange(
new Product { Name = "Kayak", Price = 275, Category = c1, Supplier = s1} ,
new Product { Name = "LifeJacket", Price = 48.95m, Category = c1, Supplier = s1} ,
new Product { Name = "Soccer ball", Price = 19.50m, Category= c2, Supplier = s2} ,
new Product { Name = "Corner Flags", Price = 34.95m, Category= c2, Supplier = s2} ,
new Product { Name = "Stadium", Price = 79500, Category= c2, Supplier = s2} ,
new Product { Name = "Thinking Cap", Price = 16, Category= c3, Supplier = s3} ,
new Product { Name = "Unsteady Chair", Price = 29.95m, Category= c3, Supplier = s3} ,
new Product { Name = "Human Chess Board", Price = 75, Category= c3, Supplier = s3} ,
new Product { Name = "Bling-Bling King", Price = 1200, Category= c3, Supplier = s3}
);
context.SaveChanges();
}
}
}
}
使用 Sql Server LocalDB,将数据库连接字符串写在 appsettings.json 文件。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings" : {"ProductConnection" : "Server=(localdb)\\MSSQLLocalDB;Database=Products;MultipleActiveResultSets=True"}
}
在 Startup.cs 的 Configure() 方法中进行配置:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApp.Models;
namespace WebApp
{
public class Startup
{
public Startup(IConfiguration config)
{
Configuration = config;
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(opts =>
{
opts.UseSqlServer(Configuration["ConnectionStrings:ProductConnection"]);
//opts.EnableSensitiveDataLogging(true);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext context)
{
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
SeedData.SeedDatabase(context);
}
}
}
注意:Configure() 方法在默认代码的基础上增加了 DbContext 参数,目的是启动的时候自动运行向数据库添加种子数据。
打开 Package Manager Console (Tools > Nuget Package Manager > Package Manager Console),运行下面两个命令:
Add-migration initDatabae
update-database
Add-migration 命令将创建迁移脚本
update-database 将未提交的变化提交到数据库。
运行之后,使用 SQL Server Object Explorer (View > SQL Server Object Explore)可以看到,数据库和表都已经正确创建。
希望实现的效果是 Http Get 请求 /test 能返回三张表的数据条数。
创建 TestMiddleware 中间件:
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Threading.Tasks;
using WebApp.Models;
namespace WebApp
{
public class TestMiddleware
{
private RequestDelegate nextDelegate;
public TestMiddleware(RequestDelegate nextDelegate)
{
this.nextDelegate = nextDelegate;
}
public async Task Invoke(HttpContext context, DataContext dataContext)
{
if (context.Request.Path == "/test") {
await context.Response.WriteAsync($"There are {dataContext.Products.Count()} prodcts.\n");
await context.Response.WriteAsync($"There are {dataContext.Categories.Count()} categories.\n");
await context.Response.WriteAsync($"There are {dataContext.Suppliers.Count()} suppliers.\n");
} else {
await nextDelegate(context);
}
}
}
}
在 Startup.cs 文件中配置中间件。
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApp.Models;
namespace WebApp
{
public class Startup
{
public Startup(IConfiguration config)
{
Configuration = config;
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(opts =>
{
opts.UseSqlServer(Configuration["ConnectionStrings:ProductConnection"]);
//opts.EnableSensitiveDataLogging(true);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext context)
{
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseMiddleware<TestMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
SeedData.SeedDatabase(context);
}
}
}