blazor 项目无法调试
CRUD (Create, Read, Update, Delete) are features that must be present in an application that uses a database. In the previous article, we discussed how to perform CRUD operations for a single table, Publisher
table.
CRUD(创建,读取,更新,删除)是使用数据库的应用程序中必须具有的功能。 在上一篇文章中,我们讨论了如何对单个表Publisher
表执行CRUD操作。
This article discusses how to create a CRUD operation involving the two tables, Book
table and Publisher
table, with the following N:1 (many-to-one) relationship.
本文讨论如何创建具有以下N:1(多对一)关系的涉及两个表Book
表和Publisher
表的CRUD操作。
CRUD页面概述 (Overview of the CRUD Pages)
Below is a page of the book list involving Book
table and Publisher
table. The first four columns are book attributes, and the fifth column is the publisher attribute.
以下是涉及Book
表和Publisher
表的书列表页面。 前四列是书籍属性,第五列是出版者属性。
The list is sorted descending by Purchase Date. By clicking on the column title, Purchase Date, the order changes from descending to ascending. The same applies to other columns.
该列表按购买日期降序排序。 通过单击列标题“ 购买日期” ,订单从降序更改为升序。 其他列也一样。
Navigation button:
导航按钮:
Navigation button:
◄
go to the previous page导航按钮:
◄
转到上一页Navigation button:
◄
go to the previous page►
go to the next page导航按钮:
◄
转到上一页►
转到下一页Navigation button:
◄
go to the previous page►
go to the next page1
,2
,3
, ... page number导航按钮:
◄
去前一页►
进入下一个页面1
,2
,3
,...页码To search books base on title criterium, type a title in the search box. The title does not have to complete.
要根据标题标准搜索书籍,请在搜索框中输入标题。 标题不必填写。
To add new data, click
Add new data
要添加新数据,请单击
Add new data
To change existing data, click
Edit
要更改现有数据,请单击“
Edit
To delete, click
Delete
要删除,请单击
Delete
Below is a page to add a book involving Book
table and Publisher
table. All data input is book attributes. Pay attention to the last data input, Publisher. The last attribute of the book is input by selecting from the option list of publishers that are taken from the Publisher
table.
下面是添加涉及Book
表和Publisher
表的书的页面。 所有数据输入均为书籍属性。 注意最后的数据输入Publisher 。 本书的最后一个属性是通过从Publisher
表中获取的Publisher
选项列表中进行选择来输入的。
The following is a page to edit a book involving Book
table and Publisher
table. All data is book attributes. Pay attention to the last data, Publisher. The last attribute of the book is updated by selecting from the option list of publishers that are taken from the Publisher
table.
以下是编辑涉及Book
表和Publisher
表的书的页面。 所有数据均为书籍属性。 注意最后一个数据Publisher 。 通过从Publisher
表中选择发布者的选项列表,可以更新书籍的最后一个属性。
Book
表: Add Data
和创建存储过程 (Book
Table: Add Data
and Create Stored Procedures)
First of all, we need to write and execute SQL scripts for(1) adding data,(2) creating a stored procedure for adding new data, and(3) creating a stored procedure for updating data.
首先,我们需要编写和执行以下SQL脚本:(1)添加数据,(2)创建用于添加新数据的存储过程,以及(3)创建用于更新数据的存储过程。
- Open SSMS. 打开SSMS。
Select the server name, click
Connect
选择服务器名称,单击“
Connect
Open a new query editor by pressing
Ctrl+N
or selecting menu:通过按
Ctrl+N
或选择菜单来打开新的查询编辑器:Open a new query editor by pressing
Ctrl+N
or selecting menu:File
|New
|Query with Current Connection
按打开一个新的查询编辑器
Ctrl+N
或选择菜单:File
|New
|Query with Current Connection
Copy the scripts, paste it into the query editor.
复制脚本,将其粘贴到查询编辑器中。
USE [BookDB]
GOINSERT INTO dbo.Book(ISBN,PubYear,PurchDate,Title,PubId)VALUES ('9789791339957', 2013, '2019-10-01', 'Pranata Sosial', 6),
('9781292061184', 2015, '2018-02-12', 'Database Systems',9),
('9786024474348', 2019, '2020-08-17',
'Desain Basis Data Akademik Perguruan Tinggi', 11),
('9781305576766', 2015, '2018-12-31',
'NoSQL Web Development with Apache Cassandra', 10),
('9781484255087', 2019, '2019-10-01',
'Beginning Database Programming Using ASP.NET Core 3', 3),
('9781484231258', 2018, '2019-11-25',
'Expert Apache Cassandra Administration', 3),
('9781789619768', 2020, '2020-03-25',
'Modern Web Development with ASP.NET Core 3', 5),
('9781492056812', 2020, '2020-06-30',
'Programming C# 8.0', 7),
('9781783989201', 2015, '2020-07-23',
'Learning Apache Cassandra', 5),
('9781484259276', 2020, '2020-08-07',
'Microsoft Blazor: Building Web App in .NET', 3),
('9786020338682', 2017, '2020-08-10', 'Disruption', 2)
GOCREATE PROCEDURE [dbo].[spAddBook]@ISBN bigint,
@Title varchar(80),
@PubYear smallint,
@PurchDate date,
@PubId int
AS
BEGIN
INSERT INTO dbo.Book(ISBN,Title,PubYear,PurchDate,PubId)
VALUES (@ISBN, @Title, @PubYear, @PurchDate, @PubId)
SELECT @ISBN AS bookId;
END
GOCREATE PROCEDURE [dbo].[spUpdateBook]@ISBN bigint,
@Title varchar(80),
@PubYear smallint,
@PurchDate date,
@PubId int
AS
UPDATE Book
SET [Title] = @Title,
[PubYear] = @PubYear,
[PurchDate] = @PurchDate,
[PubId] = @PubId
WHERE [ISBN] = @ISBN
GO
Select menu:
Query
|Execute
, or pressF5
key to execute the script above.选择菜单:
Query
|Execute
,或按F5
键执行上面的脚本。
支持CRUD操作的文件 (Files Supporting CRUD Operation)
Besides SQL script above, CRUD operation need files of entities, interfaces, implementation of interfaces, razor components, and code modification of some existing files.
除上面SQL脚本外,CRUD操作还需要实体,接口,接口的实现,剃刀组件以及一些现有文件的代码修改的文件。
实体文件 (Entity Files)
There are two entity files, Book.cs and BookPub.cs, placed in the Entities
folder.
在Entities
文件夹中放置了两个实体文件Book.cs和BookPub.cs 。
Book.csThe Book.cs
is a mapping of the Book table in the database.
Book.cs Book.cs
是数据库中Book表的映射。
On the
Solution Explorer
window, right-clickEntities
, then clickAdd
|Class
在“
Solution Explorer
窗口中,右键单击“Entities
,然后单击“Add
|“确定”。Class
Type
Book.cs
as the file name, clickAdd
.Book.cs
作为文件名,单击Add
。Click
Book.cs
to open the file, then copy and paste the following code.单击
Book.cs
打开文件,然后复制并粘贴以下代码。
using System;
using System.ComponentModel.DataAnnotations;namespace BookApp.Entities
{public class Book
{
[Key]
public long ISBN { get; set; }
public string Title { get; set; }
public short PubYear { get; set; }
public DateTime PurchDate { get; set; }
public int PubId { get; set; }
}
}
BookPub.csThe BookPub.cs
is a mapping of the joining Book table and Publisher table. The BookPub.cs
inherits properties from Book.cs
. Copy and paste the following code.
BookPub.cs BookPub.cs
是联接的Book表和Publisher表的映射。 BookPub.cs
从Book.cs
继承属性。 复制并粘贴以下代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;namespace BookApp.Entities
{ public class BookPub: Book
{
public string PubName { get; set; }
}
}
接口文件:IBookService.cs (Interface File: IBookService.cs)
IBookService.cs
declares book methods, placed in theInterfaces
folder.IBookService.cs
声明放置在Interfaces
文件夹中的书籍方法。In the
Interfaces
folder, create theIBookService.cs
file, then copy and paste the following code.在“
Interfaces
文件夹中,创建IBookService.cs
文件,然后复制并粘贴以下代码。
using BookApp.Entities;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;namespace BookApp.Interfaces
{public interface IBookService
{
Task<long> Create(Book book);
Task<int> Delete(long Id);
Task<int> Count(string search);
Task<int> Update(Book book);
Task<Book> GetById(long Id);
Task<List<BookPub>> ListAll(int skip, int take,
string orderBy, string direction, string search);
}
}
方法文件:BookService.cs (Method File: BookService.cs)
BookService.cs
implements the methods declared in theIBookService.cs
file, placed in theData
folder.BookService.cs
实现了IBookService.cs
文件中声明的方法,该文件位于Data
文件夹中。In the
Data
folder, create theBookService.cs
file, then copy and paste the following code.在“
Data
文件夹中,创建BookService.cs
文件,然后复制并粘贴以下代码。
using BookApp.Interfaces;
using BookApp.Entities;
using Dapper;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;namespace BookApp.Data
{public class BookService : IBookService
{
private readonly IDapperService _dapperService; public BookService(IDapperService dapperService)
{
this._dapperService = dapperService;
} public Task<long> Create(Book book)
{
var dbPara = new DynamicParameters();
dbPara.Add("ISBN", book.ISBN, DbType.Int64);
dbPara.Add("Title", book.Title, DbType.String);
dbPara.Add("PubYear", book.PubYear, DbType.Int16);
dbPara.Add("PurchDate", book.PurchDate, DbType.Date);
dbPara.Add("PubId", book.PubId, DbType.Int32);
var bookId = Task.FromResult(_dapperService.Insert<long>
("[dbo].[spAddBook]",dbPara, commandType:
CommandType.StoredProcedure));
return bookId;
}public Task<Book> GetById(long id)
{
var book = Task.FromResult(_dapperService.Get<Book>
($"select * from [Book] where ISBN = {id}",
null,commandType: CommandType.Text));
return book;
} public Task<int> Delete(long id){
var deleteBook = Task.FromResult(_dapperService.
Execute($"Delete [Book] where ISBN = {id}",
null,commandType: CommandType.Text));
return deleteBook;
} public Task<int> Count(string search){
var totBook = Task.FromResult(_dapperService.Get<int>
($"select COUNT(*) from Book WHERE Title like
'%{search}%'",null,commandType: CommandType.Text));
return totBook;
} public Task<List<BookPub>> ListAll(int skip, int take,
string orderBy, string direction = "DESC",
string search = ""){
var books = Task.FromResult(_dapperService.
GetAll<BookPub>($"SELECT b.*, p.Name PubName FROM
Book b LEFT OUTER JOIN Publisher p ON
b.PubId=p.Id WHERE Title like '%{search}%'
ORDER BY {orderBy} {direction} OFFSET {skip}
ROWS FETCH NEXT {take} ROWS ONLY;",null,
commandType: CommandType.Text));
return books;
} public Task<int> Update(Book book){
var dbPara = new DynamicParameters();
dbPara.Add("ISBN", book.ISBN, DbType.Int64);
dbPara.Add("Title", book.Title, DbType.String);
dbPara.Add("PubYear", book.PubYear, DbType.Int16);
dbPara.Add("PurchDate", book.PurchDate, DbType.Date);
dbPara.Add("PubId", book.PubId, DbType.Int32);
var updateBook = Task.FromResult(_dapperService.
Update<int>("[dbo].[spUpdateBook]",dbPara,
commandType:CommandType.StoredProcedure));
return updateBook;
}
}
}
组件文件*。 剃刀 (Component Files *. Razor)
There are three razor files added to the folder Page
, that is AddBook.razor
, EditBook.razor
, and FetchBook.razor
. All three contain code for I/O of the CRUD page.
文件夹Page
添加了三个剃刀文件 ,分别是AddBook.razor
, EditBook.razor
和FetchBook.razor
。 所有这三个都包含CRUD页的I / O代码。
AddBook.razor
AddBook.razor
It’s used to create data for a new book.
它用于为新书创建数据。
@page "/addBook"
@inject IBookService bookService
@inject IPublisherService publisherService
@inject Microsoft.AspNetCore.Components.NavigationManager navigationManager<h3>
Add Book
</h3><form>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label for="ISBN" class="control-label">ISBN</label>
<input for="ISBN" class="form-control"
@bind="@book.ISBN" onfocus="this.value=''" />
</div>
<div class="form-group">
<label for="Title" class="control-label">Title</label>
<input for="Title" class="form-control"
@bind="@book.Title" />
</div>
<div class="form-group">
<label for="PubYear" class="control-label">
Publication Year</label>
<input for="PubYear" class="form-control"
@bind="@book.PubYear" onfocus="this.value=''" />
</div>
<div class="form-group">
<label for="PurchDate" class="control-label">
Purchase Date</label>
<input type="date" class="form-control"
@bind="@book.PurchDate" onfocus="this.value=''" />
</div>
<div class="form-group">
<label for="Publisher" class="control-label">
Publisher</label>
<select for="Publisher" class="form-control"
@bind="@book.PubId">
<option value=0 selected>[Select Publisher]</option>
@foreach (var publisher in publishers)
{
<option value="@publisher.Id">@publisher.Name</option>
}
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<button type="button" class="btn btn-primary"
@onclick="() => CreateBook()"> Save </button>
<button type="button" class="btn btn-warning"
@onclick="() => cancel()">Cancel</button>
</div>
</div>
</div>
</form>@code {
Book book = new Book();
List<Publisher> publishers = new List<Publisher>(); protected override async Task OnInitializedAsync(){
book.ISBN = 1234567890123;
book.PubYear = (short)DateTime.Now.Year;
book.PurchDate = DateTime.Now;
publishers = await publisherService.FetchAll();
} protected async Task CreateBook(){
await bookService.Create(book);
navigationManager.NavigateTo("/booklist");
} void cancel(){
navigationManager.NavigateTo("/booklist");
}
}
EditBook.razor
EditBook.razor
It’s used to update the data of a book.
用于更新书籍的数据。
@page "/editBook/{isbn:long}"
@inject IBookService bookService
@inject IPublisherService publisherService
@inject Microsoft.AspNetCore.Components.NavigationManager navigationManager<h3>
Edit Book
</h3><form>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label for="ISBN" class="control-label">ISBN</label>
<input for="ISBN" class="form-control"
@bind="@book.ISBN" />
</div>
<div class="form-group">
<label for="Title" class="control-label">Title</label>
<input for="Title" class="form-control"
@bind="@book.Title" />
</div>
<div class="form-group">
<label for="PubYear" class="control-label">
Publication Year</label>
<input for="PubYear" class="form-control"
@bind="@book.PubYear" />
</div>
<div class="form-group">
<label for="PurchDate" class="control-label">
Purchase Date</label>
<input type="date" class="form-control"
@bind="@book.PurchDate" />
</div>
<div class="form-group">
<label for="Publisher" class="control-label">
Publisher</label>
<select for="Publisher" class="form-control"
@bind="@book.PubId">
<option value=0 >[Select Publisher]</option>
@foreach (var publisher in publishers)
{
<option value="@publisher.Id">@publisher.Name</option>
}
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<button type="button" class="btn btn-primary"
@onclick="() => UpdateBook()"> Save </button>
<button type="button" class="btn btn-warning"
@onclick="() => cancel()">Cancel</button>
</div>
</div>
</div>
</form>@code {
[Parameter]public string isbn { get; set; } Book book = new Book();
List<Publisher> publishers = new List<Publisher>(); protected override async Task OnInitializedAsync(){
book = await bookService.GetById(isbn);
publishers = await publisherService.FetchAll();
} protected async Task UpdateBook(){
await bookService.Update(book);
navigationManager.NavigateTo("/booklist");
} void cancel()
{
navigationManager.NavigateTo("/booklist");
}
}
FetchBook.razor
FetchBook.razor
It’s used to display a list of books.
它用于显示书籍列表。
@page "/booklist"
@inject IBookService bookService<link href="https://stackpath.bootstrapcdn.com/font-awesome
/4.7.0/css/font-awesome.min.css" rel="stylesheet"><style>
.sort-th {
cursor: pointer;
} .fa {
float: right;
} .btn-custom {
color: black;
float: left;
padding: 8px 16px;
text-decoration: none;
transition: background-color .3s;
border: 2px solid #000;
margin: 0px 5px 0px 5px;
}
</style><div>
<a class="btn btn-primary" href='/addBook'>Add new data</a>
</div>@if (bookModel == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="row col-md-3 pull-right">
<input type="text" id="txtSearch"
placeholder="Search Names..." class="form-control"
@bind="SearchTerm" @bind:event="oninput" />
</div>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th class="sort-th"
@onclick="@(() => SortTable("ISBN"))">
I S B N
<span class="fa @(SetSortIcon("ISBN"))"></span>
</th>
<th class="sort-th"
@onclick="@(() => SortTable("Title"))">
T i t l e
<span class="fa @(SetSortIcon("Title"))"></span>
</th>
<th class="sort-th"
@onclick="@(() => SortTable("PubYear"))">
Pub.<br />Year
<span class="fa @(SetSortIcon("PubYear"))"></span>
</th>
<th class="sort-th"
@onclick="@(() => SortTable("PurchDate"))">
Purchase<br />Date
<span class="fa @(SetSortIcon("PurchDate"))"></span>
</th>
<th class="sort-th"
@onclick="@(() => SortTable("PubName"))">
Publisher
<span class="fa @(SetSortIcon("PubName"))"></span>
</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@if (bookModel == null || bookModel.Count == 0)
{
<tr>
<td colspan="3">
No Records to display
</td>
</tr>
}
else
{
foreach (var book in bookModel)
{
<tr>
<td>@book.ISBN</td>
<td>@book.Title</td>
<td>@book.PubYear</td>
<td>@book.PurchDate.ToShortDateString()</td>
<td>@book.PubName</td>
<td>
<a class="btn btn-primary"
href='/editBook/@book.ISBN'> Edit </a>
<a class="btn btn-warning"
@onclick="() => DeleteBook(book.ISBN)">
Delete </a>
</td>
</tr>
}
}
</tbody>
</table>
<div class="pagination">
<button class="btn btn-custom" @onclick=@(async ()=>
await NavigateToPage("previous"))>◀</button> @for (int i = startPage; i <= endPage; i++)
{
var currentPage = i;
<button class="btn btn-custom pagebutton
@(currentPage==curPage?"btn-info":"")"
@onclick=@(async () =>await refreshRecords
(currentPage))> @currentPage
</button>
} <button class="btn btn-custom" @onclick=
@(async ()=>await NavigateToPage("next"))>▶</button>
</div>
}@code {
private string searchTerm;
private string SearchTerm
{
get { return searchTerm; }
set { searchTerm = value; FilterRecords(); }
} List<BookPub> bookModel;
BookPub bookEntity = new BookPub(); #region Pagination int totalPages;
int totalRecords;
int curPage;
int pagerSize;
int pageSize;
int startPage;
int endPage;
string sortColumnName = "PurchDate";
string sortDir = "DESC"; #endregion protected override async Task OnInitializedAsync()
{
//display total page buttons
pagerSize = 3;
pageSize = 5;
curPage = 1;
bookModel = await bookService.ListAll((curPage - 1) *
pageSize, pageSize, sortColumnName, sortDir, searchTerm);
totalRecords = await bookService.Count(searchTerm);
totalPages =(int)Math.Ceiling(totalRecords/(decimal)pageSize);
SetPagerSize("forward");
} protected async Task DeleteBook(long id){
await bookService.Delete(id);
bookModel = await bookService.ListAll((curPage - 1) *
pageSize, pageSize, sortColumnName, sortDir, searchTerm);
} private bool isSortedAscending;
private string activeSortColumn; private async Task<List<BookPub>>
SortRecords(string columnName, string dir){
return await bookService.ListAll((curPage - 1) * pageSize,
pageSize, columnName, dir, searchTerm);
} private async Task SortTable(string columnName){
if (columnName != activeSortColumn)
{
bookModel = await SortRecords(columnName, "ASC");
isSortedAscending = true;
activeSortColumn = columnName;
}
else
{
if (isSortedAscending)
{
bookModel = await SortRecords(columnName, "DESC");
}
else
{
bookModel = await SortRecords(columnName, "ASC");
}
isSortedAscending = !isSortedAscending;
}
sortColumnName = columnName;
sortDir = isSortedAscending ? "ASC" : "DESC";
} private string SetSortIcon(string columnName){
if (activeSortColumn != columnName)
{
return string.Empty;
}
if (isSortedAscending)
{
return "fa-sort-up";
}
else
{
return "fa-sort-down";
}
} public async Task refreshRecords(int currentPage){
bookModel = await bookService.ListAll((currentPage - 1) *
pageSize, pageSize, sortColumnName, sortDir, searchTerm);
curPage = currentPage;
this.StateHasChanged();
} public void SetPagerSize(string direction){
if (direction == "forward" && endPage < totalPages)
{
startPage = endPage + 1;
if (endPage + pagerSize < totalPages)
{
endPage = startPage + pagerSize - 1;
}
else
{
endPage = totalPages;
}
this.StateHasChanged();
}
else if (direction == "back" && startPage > 1)
{
endPage = startPage - 1;
startPage = startPage - pagerSize;
}
else
{
startPage = 1;
endPage = totalPages;
}
} public async Task NavigateToPage(string direction){
if (direction == "next")
{
if (curPage < totalPages)
{
if (curPage == endPage)
{
SetPagerSize("forward");
}
curPage += 1;
}
}
else if (direction == "previous")
{
if (curPage > 1)
{
if (curPage == startPage)
{
SetPagerSize("back");
}
curPage -= 1;
}
}
await refreshRecords(curPage);
} public void FilterRecords(){
endPage = 0;
this.OnInitializedAsync().Wait();
}
}
代码修改 (Code Modification)
File NavMenu.razorModify the code so it becomes as follows.
文件NavMenu.razor修改代码,使其如下所示。
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">Library</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div><div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="booklist">
<span class="oi oi-book"
aria-hidden="true"></span> Books
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="publisherlist">
<span class="oi oi-list-rich"
aria-hidden="true"></span> Publishers
</NavLink>
</li>
</ul>
</div>@code {
private bool collapseNavMenu = true; private string NavMenuCssClass => collapseNavMenu ?
"collapse" : null; private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
Startup.csAdd the following service.
Startup.cs添加以下服务 。
//Book service
services.AddScoped<IBookService, BookService>();
The following is the overall project structure.
以下是总体项目结构。
翻译自: https://medium.com/informatics/blazor-server-project-2-6e49418c4d18
blazor 项目无法调试