第四章 展示了从创建项目到为项目准备数据,从创建资源模型到为资源实现各种操作的过程。
第五章介绍EF Core,并通过它来实现对数据的读取与存储。
EF Core 有两种使用方式:
代码优先(Code-First)→所有对于数据库以及数据表的操作等都应该使用代码来实现,比如要为表添加一个字段,那么应该在实体类中为其添加一个属性。而EF Core会将对实体类的修改同步到数据中→Migration.不会影响数据库中现有的数据方式。
数据库优先(Database First)→在程序包管理器控制台中使用
Scaffold-DbContext命令即可根据指定的数据库生成相应的代码。
EF Core中有一个非常重要的类 DbContext
1.创建实体类
创建文件夹Entities→Author类和Book类
注意 pubic ICollection Books{get;set;} = new List();
导航属性能够为两个实体建立关系。这里是一对多的关系,即一个作者可以包含若干个图书实体。
2.创建DbContext类
3.在Startup类的方法中将LibraryDbContext添加到容器中
services.AddDbContext<LibratyDbContext>(option =>
{
option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
4.添加迁移与创建数据库
Add-Migration InitialCreation
Update-Database
命令执行成功后,此时的数据库已经创建成功了。在SQL Sever Management Studio 或者Vistual Studio 中的“SQL Server对象浏览器”查看
迁移主要用于EFCore应用程序的开发阶段,当应用程序要部署到生产环境中时,为了在生成环境创建相同的数据库或对数据库进行同样的修改,此时应在“程序包管理器控制台”中使用Script-Migration命令。执行该命令会在当前应用程序的程序集中输出目录并创建一个*.sql文件,其文件名为一串随机字符,文件的内容则是根据所有添加的迁移而生成的SQL语句
5.添加测试数据
EF Core 2.1 新增加了用于添加测试数据的API,ModelBuilder类的方法Entity()会返回一个EntityTypeBuilder对象,该对象提供了HasData方法,使用它可以将一个或多个实体对象添加到数据库中。而要得到ModelBuilder对象,则应在DbContext的派生类中重载OnModelCreating方法。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Author>().HasData(
new Author
{
Id = new Guid("72d5b5f5-3008"),
Name = "Author 1",
BirthDate = new DateTimeOffset(new DateTime(1990,11,12)),
Email = "author@xxx.com"
},...);
}
HasData方法接收一个可变参数数组,即可以添加一个或多个相同的实体类型。如果要添加的测试数据比较多,为了保存LibraryDbcontext类的简洁清晰,可以为ModelBuilder类创建扩展方法,并在扩展方法中添加数据
public static class ModelBuilderExtension
{
public static void SeedData(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>().HasData(...);
modelBuilder.Entity<Book>().HasData(...);
}
}
而在LibraryDbContext类的OnModelCreating方法中,则只需要调用这个扩展方法modelBuilder.SeedData()即可。
要让这些数据添加到数据库中,还应创建一个迁移。
①Add-Migration SeedData
命令执行成功后,在Migrations目录下会添加新的迁移类文件,以—SeedData.cs结尾。
将所有的数据更新到数据库中。
Update-Database
命令执行成功后,在数据库中就可以看到新添加的数据了。
②如果要删除测试数据,则可以删除或注释调用HasData方法的代码,并添加一个迁移即可。
Add-Migration RemoveSeededData
Update-Database
如果添加引用数据是最近一次的迁移操作,并且还没有使用Update-Database命令将修改应用到数据库中,则可以使用Remove-Migration命令删除该迁移。
6.重构仓储类
创建通用仓储接口
public interface IRepositoryBase<T>//T表示实体类
{
Task<IEnumerable<T>> GetAllAsync ();//异步方法
Task<IEnumerable<T>> GetByConditionAsync(Expression<Func<T,bool>> expression);
void Create(T entity);
void Update(T entity);
void Delete(T entity);
}
public interface IRepository2<T,TId>
{
Task<T> GetByIdAsync(Tid id);
Task<bool> IsExistAsync(TId id);//这两个方法的作用分别根据指定的实体Id获取实体
}
public class RepositoryBase<T,Tid>:IRepositoryBase<T>,IRepositoryBase2<T,TId> where T:class
{
public DbContext DbContext{get;set;}
public RepositoryBase(DbContext dbContext)
{
DbContext = dbContext;
}
public void Create(T entity)
{
DbContext.Set<T>().Add(entity);
}
public void Delete(T entity)
{
DbContext.Set<T>().Remove(entity);
}
public Task<IEnumerable<T>> GetAllAsync()
{
return Task.FormResult(DbContext.Set<T>().AsEnumerable());
}
public Task<IEnumerable><T>> GetByConditionAsync(Expression<Func<T,bool>> expression)
{
return Task.FormResult(DbContext.Set<T>().Where(expression).AsEnumable());
}
public async Task<bool> SaveAsync()
{
return await DbContext.SaveChangesAsync() > 0;
}
public void Update(T entity)
{
DbContext.Set<T>.Update(entity);
}
public async Task<T> GetByIdAsync(TId id)
{
return await DbContext.Set<T>.FindAsync(id);
}
public async Task<bool> IsExistAsync(TId id)
{
return await DbContext.Set<T>().FindAsync(id) != null;
}
}
EF Core对于查询的执行采用延迟执行的方式。
- 对结果使用for/foreach循环
- 使用了ToList(),ToArray()和ToDictionary()
- 使用了Single(),Count(),Average,First()和Max()等方法
7.创建其他仓储接口
对于每一个实体类型,应创建针对它的仓储接口及其实现。
public interface IAuthorRepository:IRepositoyrBase<Author>,IRepositoryBase2<Author,Guid>
{}
public class AuthorRepository:RepositoryBase<Author,Guid>,IAuthorRepository
{
public AuthorRepository(DbContext dbContext):base(dbContext)
{}
}
同样方式创建IBookRepository和Repository
上面所有仓储接口与实现类创建完成后,继续创建仓储包装器。
public interface IRepositoryWrapper
{
IBookRepository Book{get;}
IAuthorRepository Author{get;}
}
RepositoryWrapper类定义了一个包含LibarayDbContext类型参数的构造函数,它会使用该对象实例化其中所有的仓储类,并最终将该参数传递给RepositoryBase类
public class RepositoryWrapper:IRepositoryWrapper
{
private IAuthorRepository _authorRepository = null;
private IBookRepository _bookRepository = null;
public LibararyDbContext LibararyDbContext{get;}
public RepositoryWrapper(LibraryDbContext libraryDbContext)
{
LibraryDbContext = libararyDbContext;
}
public IAuthorRepository Author => _authorRepository?? new AuthorRepository(LibraryDbContext);
public IBookRepository Book => _bookRepository ?? new BookRepository(LibratyDbContext);
}
至此,对于仓储类的重构基本完成。接下来,要将IRepositoryWrapper及其实现放到容器中,在SetUp类的ConfigureServices方法中。
services.AddScoped<IRepositoryWrapper,RepositoryWrapper>();
8.重构Controller和Action 略