> 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);