mirror of https://github.com/abpframework/abp.git
10 changed files with 274 additions and 21 deletions
@ -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