Browse Source

Update the many-to-many relationship article

pull/10509/head
Engincan VESKE 5 years ago
parent
commit
c2d869549a
  1. 153
      docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md
  2. BIN
      docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/demo.png
  3. BIN
      docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-entity-structure.png
  4. BIN
      docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-file-structure.png

153
docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md

@ -13,11 +13,18 @@ When we've examined the ER Diagram, we can see the one-to-many relationship betw
### Source Code
You can find the source code of the application at https://github.com/EngincanV/ABP-Many-to-Many-Relationship-Demo .
### Screenshot of The Final Application
At the end of this article, we will have created an application as in the below image.
![Book Homepage](./demo.png)
## Creating the Solution
In this article, I will create a new startup template with EF Core as a database provider and MVC for UI framework.
* We can create a new startup template by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
* We can create a new startup template by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
```bash
abp new BookStore -t app --version 5.0.0-beta.2
@ -33,7 +40,7 @@ Let's start with creating our Domain Entities.
We can create a folder-structure under the `BookStore.Domain` project like in the below image.
![Domain-Layer-Folder-Structure](./domain-entity-structure.png)
![Domain-Layer-Folder-Structure](./domain-file-structure.png)
Open the entity classes and add the following codes to each of these classes.
@ -86,6 +93,10 @@ namespace BookStore.Authors
```csharp
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
namespace BookStore.Books
@ -94,70 +105,94 @@ namespace BookStore.Books
{
public Guid AuthorId { get; set; }
public string Name { get; set; }
public BookType Type { get; set; }
public string Name { get; private set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
/* This constructor is for deserialization / ORM purpose */
public ICollection<BookCategory> Categories { get; private set; }
private Book()
{
}
public Book(Guid id, Guid authorId, string name, BookType type, DateTime publishDate, float price)
public Book(Guid id, Guid authorId, string name, DateTime publishDate, float price)
: base(id)
{
AuthorId = authorId;
Name = name;
Type = type;
SetName(name);
PublishDate = publishDate;
Price = price;
Categories = new Collection<BookCategory>();
}
}
}
```
> We'll create the `BookType` enum later in this step.
public void SetName(string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), BookConsts.MaxNameLength);
}
* **Category.cs**
public void AddCategory(Guid categoryId)
{
Check.NotNull(categoryId, nameof(categoryId));
if (IsInCategory(categoryId))
{
return;
}
Categories.Add(new BookCategory(bookId: Id, categoryId));
}
```csharp
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
public void RemoveCategory(Guid categoryId)
{
Check.NotNull(categoryId, nameof(categoryId));
namespace BookStore.Categories
{
public class Category : AuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
if (!IsInCategory(categoryId))
{
return;
}
public bool IsActive { get; set; }
Categories.RemoveAll(x => x.CategoryId == categoryId);
}
/* This constructor is for deserialization / ORM purpose */
private Category()
public void RemoveAllCategoriesExceptGivenIds(List<Guid> categoryIds)
{
Check.NotNullOrEmpty(categoryIds, nameof(categoryIds));
Categories.RemoveAll(x => !categoryIds.Contains(x.CategoryId));
}
public Category(Guid id, string name, bool isActive = true) : base(id)
public void RemoveAllCategories()
{
SetName(name);
IsActive = isActive;
Categories.RemoveAll(x => x.BookId == Id);
}
public Category SetName(string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), BookConsts.MaxNameLength);
return this;
private bool IsInCategory(Guid categoryId)
{
return Categories.Any(x => x.CategoryId == categoryId);
}
}
}
```
> We'll create the `BookConsts` class later in this step.
* In our scenerio, a book can have more than one category and a category can have more
than one book so it's obvious to we need to establish many-to-many relationship between them.
* For achieve this we will create an **join entity** named `BookCategory`, and this class
will simply have variables named `BookId` and `CategoryId`.
* To manage this **join entity**, we can add it as sub-collection to the **Book** entity, as we do above. We add this sub-collection
to **Book** class instead of **Category** class, because a book can have tens or mostly hundreds of category in it but in other
view a category can have more than hundred (or even way much) book inside of it.
* It is a significant performance problem to load thousands of items whenever you query a category. Therefore it's make much sense to add that sub-collection to `Book` entity. (Don't forget: **An aggregate (with the root entity and sub-collections) should
be serializable and transferrable on the wire as a single unit.**)
* Notice that, `BookCategory` is not an **Aggregate Root** so we are not violating the one of the base rule about Aggregate Root (Rule: "Reference Other Aggregates Only by ID").
> We'll create the `BookCategory` and `BookConsts` classes later in this step.
* **BookCategory.cs**
@ -173,6 +208,7 @@ namespace BookStore.Books
public Guid CategoryId { get; protected set; }
/* This constructor is for deserialization / ORM purpose */
private BookCategory()
{
}
@ -191,27 +227,52 @@ namespace BookStore.Books
}
```
Here, as you can notice we've defined the `BookCategory` as the **Join Table** for our many-to-many relationship and ensure the required properties (BookId and CategoryId) must be set to create this object.
* Here, as you can notice we've defined the `BookCategory` as the **Join Table/Entity** for our many-to-many relationship and ensure the required properties (BookId and CategoryId) must be set to create this object.
And also we've derived this class from `Entity` class and therefore we've had to override the **GetKeys** method of this class to define **Composite Key**
* And also we've derived this class from `Entity` class and therefore we've had to override the **GetKeys** method of this class to define **Composite Key**.
> The composite key is composed of `BookId` and `CategoryId` in our case.
> The composite key is composed of `BookId` and `CategoryId` in our case. And they are unique with together.
> For more information about **Entities with Composite Keys**, you can read the relavant section from [Entites documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys)
//TODO: add BookManager class later?
* **Category.cs**
* After defining our entities, now we can open the `BookStore.Domain.Shared` project and add the relevant constants and enums from there.
```csharp
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
*
namespace BookStore.Categories
{
public class Category : AuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
/* This constructor is for deserialization / ORM purpose */
private Category()
{
}
public Category(Guid id, string name) : base(id)
{
SetName(name);
}
public Category SetName(string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), CategoryConsts.MaxNameLength);
return this;
}
}
}
```
### Step 2 - (Database Integration)
### Step 3 - (Database Migration)
---
### Step 4 - (Create Application Services)
* Create the Domain Entities ✓
* Database Integration ✓
* Db migration ✓
* app services (category -> crud app service)
* ui
### Step 5 - (UI)

BIN
docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/demo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-entity-structure.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/domain-file-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Loading…
Cancel
Save