1.Web项目
1.1概述
本例主要介绍EF,数据初始化迁移,Knockout.js使用……
1.2创建项目
1.3添加实体类
在Models文件夹下,先添加一个Author.cs类,其代码:
using System.ComponentModel.DataAnnotations; namespace WebApi05.Models { public class Author { public int Id { get; set; } [Required] public string Name { get; set; } } }
在Models文件夹下,再添加一个Book.cs类,其代码:
using System.ComponentModel.DataAnnotations; namespace WebApi05.Models { public class Book { public int Id { get; set; } [Required] public string Title { get; set; } public int Year { get; set; } public decimal Price { get; set; } public string Genre { get; set; } // 外键 public int AuthorId { get; set; } // 导航属性 public Author Author { get; set; } } }
1.4添加控制器
这次我们使用基架模板来创建控制器。如果需要模型类和上下文类刚新建的,需要先生成一下项目,才能使得基架识得其类。
在Controllers文件夹上右键,添加-》控制器:
选择模板后,点添加:
这里先点“数据上下文类”后的+号,来新建数据库上下文设置:
然后勾选异步:
这样基架自动为项目添加了Controllers\AuthorsController.cs和Models\EFContext.cs两个类,并在Web.config中添加数据库连接字符串。
由于生成的EFContext.cs是新建的,我们又需要先生成项目。再来创建Book控制器。创建步骤和前面一样:
1.4启用迁移
vs里,工具-》NuGet程序包管理器-》程序包管理器控制器台:
输入命令:get-help migration 来查看帮助。
启用迁移命令:
注意:选择默认项目,也就是在那个项目进行迁移。
命令执行后,在根目录下生成一个Migrations文件夹,里面有一个Configuration.cs类。
1.5添加迁移
先设置初始化数据,把Migrations\Configuration.cs代码修改为:
using System.Data.Entity.Migrations; using WebApi05.Models; namespace WebApi05.Migrations { internal sealed class Configuration : DbMigrationsConfiguration<EFContext> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(EFContext context) { context.Authors.AddOrUpdate(x => x.Id, new Author() { Id = 1, Name = "Jane Austen" }, new Author() { Id = 2, Name = "Charles Dickens" }, new Author() { Id = 3, Name = "Miguel de Cervantes" }); context.Books.AddOrUpdate(x => x.Id, new Book() { Id = 1, Title = "Pride and Prejudice", Year = 1813, AuthorId = 1, Price = 9.99M, Genre = "Comedy of manners" }, new Book() { Id = 2, Title = "Northanger Abbey", Year = 1817, AuthorId = 1, Price = 12.95M, Genre = "Gothic parody" }, new Book() { Id = 3, Title = "David Copperfield", Year = 1850, AuthorId = 2, Price = 15, Genre = "Bildungsroman" }, new Book() { Id = 4, Title = "Don Quixote", Year = 1617, AuthorId = 3, Price = 8.95M, Genre = "Picaresque" }); } } }
添加迁移命令:
执行成功后,这是Migrations文件夹下会多出一个:时间戳+FirstInitData.cs类。
1.6更新数据库
输入命令:
1.7查看数据库
我们看迁移数据库是否OK。VS里,视图-》服务器资源管理器:
1.8配置迁移
通过迁移“三步走”(启用,添加,更新),我们可以手动来迁移数据库。但有时根据需要是否进行迁移初始化数据,
能不能通过配置文件了来决定(也可以在程序入口写代码执行方法,但这样改动后需要重新编译项目)。
打开Web.config,在 <entityFramework>节点下,添加下面代码:
<contexts> <!--上下文类命名空间,项目程序集名称--> <!--<context type="WebApi05.Models.EFContext,WebApi05" disableDatabaseInitialization="false">--> <context type="WebApi05.Models.EFContext,WebApi05" > <!--初始数据命名空间,项目程序集名称--> <databaseInitializer type="WebApi05.Models.InitData,WebApi05" /> </context> </contexts>
在Models文件夹下,添加InitData.cs类,其代码:
using System.Data.Entity; using System.Data.Entity.Migrations; namespace WebApi05.Models { public class InitData : DropCreateDatabaseIfModelChanges<EFContext> { protected override void Seed(EFContext context) { context.Authors.AddOrUpdate(x => x.Id, new Author() { Id = 1, Name = "Jane Austen" }, new Author() { Id = 2, Name = "Charles Dickens" }, new Author() { Id = 3, Name = "Miguel de Cervantes" }); context.Books.AddOrUpdate(x => x.Id, new Book() { Id = 1, Title = "Pride and Prejudice", Year = 1813, AuthorId = 1, Price = 9.99M, Genre = "Comedy of manners" }, new Book() { Id = 2, Title = "Northanger Abbey", Year = 1817, AuthorId = 1, Price = 12.95M, Genre = "Gothic parody" }, new Book() { Id = 3, Title = "David Copperfield", Year = 1850, AuthorId = 2, Price = 15, Genre = "Bildungsroman" }, new Book() { Id = 4, Title = "Don Quixote", Year = 1617, AuthorId = 3, Price = 8.95M, Genre = "Picaresque" }); } } }
测试:删除原来数据或更改连接字符串中数据库名称。这样程序运行涉及到数据库访问时,就可以自动迁移了。
1.9查看生成SQL语句
修改Models\EFContext.cs
using System.Data.Entity; using System.Diagnostics; namespace WebApi05.Models { public class EFContext : DbContext { public EFContext() : base("name=EFContext") { this.Database.Log = s => Debug.WriteLine(s); } public DbSet<Author> Authors { get; set; } public DbSet<Book> Books { get; set; } } }
打开vs里,视图-》输出(窗户),运行网站地址http://localhost:8935/api/books 这时输出窗户可以看到生成的SQL语句:
查询结果是(可以使用fiddler工具看JSON格式):
可以看到Author数据(模型导航属性)没有加载。修改Controllers\BooksController.cs中的GetBooks(),如下:
// GET: api/Books public IQueryable<Book> GetBooks() { return db.Books.Include(b => b.Author); }
再一次运行:
再看VS输出窗户:
如果在模型导航属性加入virtual关键字,这是查询为延迟加载(也就是多次访问数据库查询),这里就不演示。(在专门EF系列中再介绍)
1.10数据传输DTO
在Models文件夹下,创建BookDTO.cs,其代码:
namespace WebApi05.Models { public class BookDTO { public int Id { get; set; } public string Title { get; set; } public string AuthorName { get; set; } } }
在Models文件夹下,创建BookDetailDTO.cs,其代码:
namespace WebApi05.Models { public class BookDetailDTO { public int Id { get; set; } public string Title { get; set; } public int Year { get; set; } public decimal Price { get; set; } public string AuthorName { get; set; } public string Genre { get; set; } } }
1.11修改控制器
打开Controllers\BooksController.cs,修改GetBooks()和GetBook(int id):
// GET api/Books public IQueryable<BookDTO> GetBooks() { var books = from b in db.Books select new BookDTO() { Id = b.Id, Title = b.Title, AuthorName = b.Author.Name }; return books; } // GET api/Books/5 [ResponseType(typeof(BookDetailDTO))] public async Task<IHttpActionResult> GetBook(int id) { var book = await db.Books.Include(b => b.Author).Select(b => new BookDetailDTO() { Id = b.Id, Title = b.Title, Year = b.Year, Price = b.Price, AuthorName = b.Author.Name, Genre = b.Genre }).SingleOrDefaultAsync(b => b.Id == id); if (book == null) { return NotFound(); } return Ok(book); }
再一次运行:
再看vs输出生成SQL语句(简洁):
打开Controllers\BooksController.cs,修改PostBook(Book book):
[ResponseType(typeof(Book))] public async Task<IHttpActionResult> PostBook(Book book) { if (!ModelState.IsValid) { return BadRequest(ModelState); } db.Books.Add(book); await db.SaveChangesAsync(); db.Entry(book).Reference(x => x.Author).Load(); var dto = new BookDTO() { Id = book.Id, Title = book.Title, AuthorName = book.Author.Name }; return CreatedAtRoute("DefaultApi", new { id = book.Id }, dto); }
1.12Knockoutjs
安装knockoutjs(简称KO):
在Scripts文件夹下,添加app.js,其代码:
var ViewModel = function () { var self = this; self.books = ko.observableArray();//保存书籍的列表 self.error = ko.observable();//包含一条错误消息,如果 AJAX 调用失败 var booksUri = ‘/api/books/‘; //AJAX助手方法 function ajaxHelper(uri, method, data) { self.error(‘‘); // Clear error message return $.ajax({ type: method, url: uri, dataType: ‘json‘, contentType: ‘application/json‘, data: data ? JSON.stringify(data) : null }).fail(function (jqXHR, textStatus, errorThrown) { self.error(errorThrown); }); } //获取所有书籍方法 function getAllBooks() { ajaxHelper(booksUri, ‘GET‘).done(function (data) { self.books(data); }); } //获取初始数据 getAllBooks(); }; ko.applyBindings(new ViewModel());
打开 App_Start/BundleConfig.cs。将下面的代码添加到 RegisterBundles ()中:
bundles.Add(new ScriptBundle("~/bundles/app").Include( "~/Scripts/knockout-{version}.js", "~/Scripts/app.js"));
1.13修改视图
把Views\Home\Index.cshtml,修改为:
@section scripts { @Scripts.Render("~/bundles/app") } <div class="page-header"> <h1>书籍展示</h1> </div> <div class="row"> <div class="col-md-4"> <div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">书籍列表</h2> </div> <div class="panel-body"> <ul class="list-unstyled" data-bind="foreach: books"> <li> <strong><span data-bind="text: AuthorName"></span></strong>: <span data-bind="text: Title"></span> <small><a href="#">详细</a></small> </li> </ul> </div> </div> <div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div> </div> <div class="col-md-4"> <!-- TODO: Book details --> </div> <div class="col-md-4"> <!-- TODO: Add new book --> </div> </div>
运行网站:
注:上面详细页,并没有“激活”。
1.14显示详细页
修改Scripts\app.js,其代码为:
var ViewModel = function () { var self = this; self.books = ko.observableArray();//保存书籍的列表 self.error = ko.observable();//包含一条错误消息,如果 AJAX 调用失败 self.detail = ko.observable();//保存书籍详细 var booksUri = ‘/api/books/‘; //AJAX助手方法 function ajaxHelper(uri, method, data) { self.error(‘‘); // Clear error message return $.ajax({ type: method, url: uri, dataType: ‘json‘, contentType: ‘application/json‘, data: data ? JSON.stringify(data) : null }).fail(function (jqXHR, textStatus, errorThrown) { self.error(errorThrown); }); } //获取所有书籍方法 function getAllBooks() { ajaxHelper(booksUri, ‘GET‘).done(function (data) { self.books(data); }); } //获取书籍详细方法 self.getBookDetail = function (item) { ajaxHelper(booksUri + item.Id, ‘GET‘).done(function (data) { self.detail(data); }); } //获取初始数据 getAllBooks(); }; ko.applyBindings(new ViewModel());
继续把Views\Home\Index.cshtml,修改为:
@section scripts { @Scripts.Render("~/bundles/app") } <div class="page-header"> <h1>书籍展示</h1> </div> <div class="row"> <div class="col-md-4"> <div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">书籍列表</h2> </div> <div class="panel-body"> <ul class="list-unstyled" data-bind="foreach: books"> <li> <strong><span data-bind="text: AuthorName"></span></strong>: <span data-bind="text: Title"></span> <small><a href="#" data-bind="click: $parent.getBookDetail">详细</a></small> </li> </ul> </div> </div> <div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div> </div> <!-- ko if:detail() --> <div class="col-md-4"> <div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">详细</h2> </div> <table class="table"> <tr><td>Author</td><td data-bind="text: detail().AuthorName"></td></tr> <tr><td>Title</td><td data-bind="text: detail().Title"></td></tr> <tr><td>Year</td><td data-bind="text: detail().Year"></td></tr> <tr><td>Genre</td><td data-bind="text: detail().Genre"></td></tr> <tr><td>Price</td><td data-bind="text: detail().Price"></td></tr> </table> </div> </div> <!-- /ko --> <div class="col-md-4"> <!-- TODO: Add new book --> </div> </div>
运行网站:
1.15添加一个
修改Scripts\app.js,其代码为:
var ViewModel = function () { var self = this; self.books = ko.observableArray();//保存书籍的列表 self.error = ko.observable();//包含一条错误消息,如果 AJAX 调用失败 self.detail = ko.observable();//保存书籍详细 self.authors = ko.observableArray(); self.newBook = { Author: ko.observable(), Genre: ko.observable(), Price: ko.observable(), Title: ko.observable(), Year: ko.observable() } var authorsUri = ‘/api/authors/‘; //获取所有作者 function getAuthors() { ajaxHelper(authorsUri, ‘GET‘).done(function (data) { self.authors(data); }); } //添加书籍 self.addBook = function (formElement) { var book = { AuthorId: self.newBook.Author().Id, Genre: self.newBook.Genre(), Price: self.newBook.Price(), Title: self.newBook.Title(), Year: self.newBook.Year() }; ajaxHelper(booksUri, ‘POST‘, book).done(function (item) { self.books.push(item); }); } var booksUri = ‘/api/books/‘; //AJAX助手方法 function ajaxHelper(uri, method, data) { self.error(‘‘); // Clear error message return $.ajax({ type: method, url: uri, dataType: ‘json‘, contentType: ‘application/json‘, data: data ? JSON.stringify(data) : null }).fail(function (jqXHR, textStatus, errorThrown) { self.error(errorThrown); }); } //获取所有书籍方法 function getAllBooks() { ajaxHelper(booksUri, ‘GET‘).done(function (data) { self.books(data); }); } //获取书籍详细方法 self.getBookDetail = function (item) { ajaxHelper(booksUri + item.Id, ‘GET‘).done(function (data) { self.detail(data); }); } //获取初始数据 getAllBooks(); getAuthors(); }; ko.applyBindings(new ViewModel());
把Views\Home\Index.cshtml,修改为:
@section scripts { @Scripts.Render("~/bundles/app") } <div class="page-header"> <h1>书籍展示</h1> </div> <div class="row"> <div class="col-md-4"> <div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">书籍列表</h2> </div> <div class="panel-body"> <ul class="list-unstyled" data-bind="foreach: books"> <li> <strong><span data-bind="text: AuthorName"></span></strong>: <span data-bind="text: Title"></span> <small><a href="#" data-bind="click: $parent.getBookDetail">详细</a></small> </li> </ul> </div> </div> <div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div> </div> <!-- ko if:detail() --> <div class="col-md-4"> <div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">详细</h2> </div> <table class="table"> <tr><td>Author</td><td data-bind="text: detail().AuthorName"></td></tr> <tr><td>Title</td><td data-bind="text: detail().Title"></td></tr> <tr><td>Year</td><td data-bind="text: detail().Year"></td></tr> <tr><td>Genre</td><td data-bind="text: detail().Genre"></td></tr> <tr><td>Price</td><td data-bind="text: detail().Price"></td></tr> </table> </div> </div> <!-- /ko --> <div class="col-md-4"> <div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">Add Book</h2> </div> <div class="panel-body"> <form class="form-horizontal" data-bind="submit: addBook"> <div class="form-group"> <label for="inputAuthor" class="col-sm-2 control-label">Author</label> <div class="col-sm-10"> <select data-bind="options:authors, optionsText: ‘Name‘, value: newBook.Author"></select> </div> </div> <div class="form-group" data-bind="with: newBook"> <label for="inputTitle" class="col-sm-2 control-label">Title</label> <div class="col-sm-10"> <input type="text" class="form-control" id="inputTitle" data-bind="value:Title" /> </div> <label for="inputYear" class="col-sm-2 control-label">Year</label> <div class="col-sm-10"> <input type="number" class="form-control" id="inputYear" data-bind="value:Year" /> </div> <label for="inputGenre" class="col-sm-2 control-label">Genre</label> <div class="col-sm-10"> <input type="text" class="form-control" id="inputGenre" data-bind="value:Genre" /> </div> <label for="inputPrice" class="col-sm-2 control-label">Price</label> <div class="col-sm-10"> <input type="number" step="any" class="form-control" id="inputPrice" data-bind="value:Price" /> </div> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div> </div> </div>
网站运行:
2.小结
本例重点掌握EF,数据迁移,使用DTO,Knockout绑定模型输出DOM上。
随着学习深度,是不是越来越接近高大上感觉了。到目前为止,还是皮毛哦。