首页 > 代码库 > WebApi学习笔记05:使用webapi模板--实体类--EF--迁移--Knockout

WebApi学习笔记05:使用webapi模板--实体类--EF--迁移--Knockout

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; }    }}
View Code

 在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; }    }}
View Code

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"                  });        }    }}
View Code

添加迁移命令:

执行成功后,这是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>
View Code

在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"                  });        }    }}
View Code

测试:删除原来数据或更改连接字符串中数据库名称。这样程序运行涉及到数据库访问时,就可以自动迁移了。

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; }    }}
View Code

打开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);        }
View Code

再一次运行:

再看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; }    }}
View Code

在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; }    }}
View Code

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);        } 
View Code

再一次运行:

再看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);        }
View Code

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());
View Code

打开 App_Start/BundleConfig.cs。将下面的代码添加到 RegisterBundles ()中:

            bundles.Add(new ScriptBundle("~/bundles/app").Include(              "~/Scripts/knockout-{version}.js",              "~/Scripts/app.js"));
View Code

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=http://www.mamicode.com/"#">详细</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>
View Code

运行网站:

注:上面详细页,并没有“激活”。

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());
View Code

继续把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=http://www.mamicode.com/"#" 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>
View Code

运行网站:

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());
View Code

把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=http://www.mamicode.com/"#" 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>
View Code

网站运行:

2.小结

本例重点掌握EF,数据迁移,使用DTO,Knockout绑定模型输出DOM上。

随着学习深度,是不是越来越接近高大上感觉了。到目前为止,还是皮毛哦。

 

WebApi学习笔记05:使用webapi模板--实体类--EF--迁移--Knockout