mirror of https://github.com/abpframework/abp
parent
56a653db10
commit
1fb29ca83c
@ -0,0 +1,231 @@
|
|||||||
|
# Web Application Development Tutorial - Part 10: Book to Author Relation
|
||||||
|
````json
|
||||||
|
//[doc-params]
|
||||||
|
{
|
||||||
|
"UI": ["MVC","NG"],
|
||||||
|
"DB": ["EF","Mongo"]
|
||||||
|
}
|
||||||
|
````
|
||||||
|
{{
|
||||||
|
if UI == "MVC"
|
||||||
|
UI_Text="mvc"
|
||||||
|
else if UI == "NG"
|
||||||
|
UI_Text="angular"
|
||||||
|
else
|
||||||
|
UI_Text="?"
|
||||||
|
end
|
||||||
|
if DB == "EF"
|
||||||
|
DB_Text="Entity Framework Core"
|
||||||
|
else if DB == "Mongo"
|
||||||
|
DB_Text="MongoDB"
|
||||||
|
else
|
||||||
|
DB_Text="?"
|
||||||
|
end
|
||||||
|
}}
|
||||||
|
|
||||||
|
## About This Tutorial
|
||||||
|
|
||||||
|
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies:
|
||||||
|
|
||||||
|
* **{{DB_Text}}** as the ORM provider.
|
||||||
|
* **{{UI_Value}}** as the UI Framework.
|
||||||
|
|
||||||
|
This tutorial is organized as the following parts;
|
||||||
|
|
||||||
|
- [Part 1: Creating the server side](Part-1.md)
|
||||||
|
- [Part 2: The book list page](Part-2.md)
|
||||||
|
- [Part 3: Creating, updating and deleting books](Part-3.md)
|
||||||
|
- [Part 4: Integration tests](Part-4.md)
|
||||||
|
- [Part 5: Authorization](Part-5.md)
|
||||||
|
- [Part 6: Authors: Domain layer](Part-6.md)
|
||||||
|
- [Part 7: Authors: Database Integration](Part-7.md)
|
||||||
|
- [Part 8: Authors: Application Layer](Part-8.md)
|
||||||
|
- [Part 9: Authors: User Interface](Part-9.md)
|
||||||
|
- **Part 10: Book to Author Relation (this part)**
|
||||||
|
|
||||||
|
### Download the Source Code
|
||||||
|
|
||||||
|
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
|
||||||
|
|
||||||
|
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
|
||||||
|
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
We have created `Book` and `Author` functionalities for the book store application. However, currently there is no relation between these entities.
|
||||||
|
|
||||||
|
In this tutorial, we will establish a **1 to N** relation between the `Book` and the `Author`.
|
||||||
|
|
||||||
|
## Add Relation to The Book Entity
|
||||||
|
|
||||||
|
Open the `Books/Book.cs` in the `Acme.BookStore.Domain` project and add the following property to the `Book` entity:
|
||||||
|
|
||||||
|
````csharp
|
||||||
|
public Guid AuthorId { get; set; }
|
||||||
|
````
|
||||||
|
|
||||||
|
## Database Migration
|
||||||
|
|
||||||
|
Added a new, required `AuthorId` property to the `Book` entity. But, what about the existing books on the database? They currently don't have `AuthorId`s and this will be a problem when we try to run the application.
|
||||||
|
|
||||||
|
This is a typical migration problem and the decision depends on your case;
|
||||||
|
|
||||||
|
* If you haven't published your application to the production yet, you can just delete existing books in the database, or you can even delete the entire database in your development environment.
|
||||||
|
* You can do it programmatically on data migration or seed phase.
|
||||||
|
* You can manually handle it on the database.
|
||||||
|
|
||||||
|
We prefer to drop the database (run the `Drop-Database` in the *Package Manager Console*) since this is just an example project and data loss is not important. Since this topic is not related to the ABP Framework, we don't go deeper for all scenarios.
|
||||||
|
|
||||||
|
{{if DB=="EF"}}
|
||||||
|
|
||||||
|
### Update the EF Core Mapping
|
||||||
|
|
||||||
|
Open the `BookStoreDbContextModelCreatingExtensions` class under the `EntityFrameworkCore` folder of the `Acme.BookStore.EntityFrameworkCore` project and change the `builder.Entity<Book>` part as shown below:
|
||||||
|
|
||||||
|
````csharp
|
||||||
|
builder.Entity<Book>(b =>
|
||||||
|
{
|
||||||
|
b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema);
|
||||||
|
b.ConfigureByConvention(); //auto configure for the base class props
|
||||||
|
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||||
|
|
||||||
|
// ADD THE MAPPING FOR THE RELATION
|
||||||
|
b.HasOne<Author>().WithMany().HasForeignKey(x => x.AuthorId).IsRequired();
|
||||||
|
});
|
||||||
|
````
|
||||||
|
|
||||||
|
### Add New EF Core Migration
|
||||||
|
|
||||||
|
Run the following command in the Package Manager Console (of the Visual Studio) to add a new database migration:
|
||||||
|
|
||||||
|
````bash
|
||||||
|
Add-Migration "Added_AuthorId_To_Book"
|
||||||
|
````
|
||||||
|
|
||||||
|
This should create a new migration class with the following code in its `Up` method:
|
||||||
|
|
||||||
|
````csharp
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "AuthorId",
|
||||||
|
table: "AppBooks",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AppBooks_AuthorId",
|
||||||
|
table: "AppBooks",
|
||||||
|
column: "AuthorId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_AppBooks_AppAuthors_AuthorId",
|
||||||
|
table: "AppBooks",
|
||||||
|
column: "AuthorId",
|
||||||
|
principalTable: "AppAuthors",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
````
|
||||||
|
|
||||||
|
* Adds an `AuthorId` field to the `AppBooks` table.
|
||||||
|
* Creates an index on the `AuthorId` field.
|
||||||
|
* Declares the foreign key to the `AppAuthors` table.
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
## Change the Data Seeder
|
||||||
|
|
||||||
|
Since the `AuthorId` is a required property of the `Book` entity, current data seeder code can not work. Open the `BookStoreDataSeederContributor` in the `Acme.BookStore.Domain` project and change as the following:
|
||||||
|
|
||||||
|
````csharp
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Acme.BookStore.Authors;
|
||||||
|
using Acme.BookStore.Books;
|
||||||
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace Acme.BookStore
|
||||||
|
{
|
||||||
|
public class BookStoreDataSeederContributor
|
||||||
|
: IDataSeedContributor, ITransientDependency
|
||||||
|
{
|
||||||
|
private readonly IRepository<Book, Guid> _bookRepository;
|
||||||
|
private readonly IAuthorRepository _authorRepository;
|
||||||
|
private readonly AuthorManager _authorManager;
|
||||||
|
|
||||||
|
public BookStoreDataSeederContributor(
|
||||||
|
IRepository<Book, Guid> bookRepository,
|
||||||
|
IAuthorRepository authorRepository,
|
||||||
|
AuthorManager authorManager)
|
||||||
|
{
|
||||||
|
_bookRepository = bookRepository;
|
||||||
|
_authorRepository = authorRepository;
|
||||||
|
_authorManager = authorManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SeedAsync(DataSeedContext context)
|
||||||
|
{
|
||||||
|
if (await _bookRepository.GetCountAsync() > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var orwell = await _authorRepository.InsertAsync(
|
||||||
|
await _authorManager.CreateAsync(
|
||||||
|
"George Orwell",
|
||||||
|
new DateTime(1903, 06, 25),
|
||||||
|
"Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949)."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
var douglas = await _authorRepository.InsertAsync(
|
||||||
|
await _authorManager.CreateAsync(
|
||||||
|
"Douglas Adams",
|
||||||
|
new DateTime(1952, 03, 11),
|
||||||
|
"Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed 'radical atheist'."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await _bookRepository.InsertAsync(
|
||||||
|
new Book
|
||||||
|
{
|
||||||
|
AuthorId = orwell.Id, // SET THE AUTHOR
|
||||||
|
Name = "1984",
|
||||||
|
Type = BookType.Dystopia,
|
||||||
|
PublishDate = new DateTime(1949, 6, 8),
|
||||||
|
Price = 19.84f
|
||||||
|
},
|
||||||
|
autoSave: true
|
||||||
|
);
|
||||||
|
|
||||||
|
await _bookRepository.InsertAsync(
|
||||||
|
new Book
|
||||||
|
{
|
||||||
|
AuthorId = douglas.Id, // SET THE AUTHOR
|
||||||
|
Name = "The Hitchhiker's Guide to the Galaxy",
|
||||||
|
Type = BookType.ScienceFiction,
|
||||||
|
PublishDate = new DateTime(1995, 9, 27),
|
||||||
|
Price = 42.0f
|
||||||
|
},
|
||||||
|
autoSave: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
````
|
||||||
|
|
||||||
|
The only change is that we set the `AuthorId` properties of the `Book` entities.
|
||||||
|
|
||||||
|
{{if DB=="EF"}}
|
||||||
|
|
||||||
|
You can now run the `.DbMigrator` console application to **migrate** the **database schema** and **seed** the initial data.
|
||||||
|
|
||||||
|
{{else if DB="Mongo"}}
|
||||||
|
|
||||||
|
You can now run the `.DbMigrator` console application to **seed** the initial data.
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
## Application Layer
|
||||||
|
|
||||||
|
TODO
|
Loading…
Reference in new issue