6.0 KiB
Many to Many Relationship with ABP and EF Core
Introduction
In this article, we'll create a BookStore application like in the ABP tutorial and add an extra Category feature to demonstrate how we can manage the many-to-many relationship with ABP-based applications (by following DDD rules).
You can see the ER Diagram of our application under below. This diagram will be helpful for us to demonstrate the relations between our entities.
When we've examined the ER Diagram, we can see the one-to-many relationship between Author and Book tables and also the many-to-many relationship (BookCategory table) between Book and Category tables. Cause, there can be more than one category on each book and vice-versa in our scenario.
Source Code
You can find the source code of the application at https://github.com/EngincanV/ABP-Many-to-Many-Relationship-Demo .
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:
abp new BookStore -t app --version 5.0.0-beta.2
- Our project boilerplate will be ready after the download is finished. Then, we can open the solution and starts to the development.
Starting the Development
Let's start with creating our Domain Entities.
Step 1 - (Creating the Domain Entities)
We can create a folder-structure under the BookStore.Domain project like in the below image.
Open the entity classes and add the following codes to each of these classes.
- Author.cs
using System;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
namespace BookStore.Authors
{
public class Author : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
/* This constructor is for deserialization / ORM purpose */
private Author()
{
}
public Author(Guid id, [NotNull] string name, DateTime birthDate, [CanBeNull] string shortBio = null)
: base(id)
{
SetName(name);
BirthDate = birthDate;
ShortBio = shortBio;
}
public void SetName([NotNull] string name)
{
Name = Check.NotNullOrWhiteSpace(
name,
nameof(name),
maxLength: AuthorConsts.MaxNameLength
);
}
}
}
We'll create the
AuthorConstsclass later in this step.
- Book.cs
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace BookStore.Books
{
public class Book : FullAuditedAggregateRoot<Guid>
{
public Guid AuthorId { get; set; }
public string Name { get; set; }
public BookType Type { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
/* This constructor is for deserialization / ORM purpose */
private Book()
{
}
public Book(Guid id, Guid authorId, string name, BookType type, DateTime publishDate, float price)
: base(id)
{
AuthorId = authorId;
Name = name;
Type = type;
PublishDate = publishDate;
Price = price;
}
}
}
We'll create the
BookTypeenum later in this step.
- Category.cs
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; }
public bool IsActive { get; set; }
/* This constructor is for deserialization / ORM purpose */
private Category()
{
}
public Category(Guid id, string name, bool isActive = true) : base(id)
{
SetName(name);
IsActive = isActive;
}
public Category SetName(string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), BookConsts.MaxNameLength);
return this;
}
}
}
We'll create the
BookConstsclass later in this step.
- BookCategory.cs
using System;
using Volo.Abp.Domain.Entities;
namespace BookStore.Books
{
public class BookCategory : Entity
{
public Guid BookId { get; protected set; }
public Guid CategoryId { get; protected set; }
private BookCategory()
{
}
public BookCategory(Guid bookId, Guid categoryId)
{
BookId = bookId;
CategoryId = categoryId;
}
public override object[] GetKeys()
{
return new object[] {BookId, CategoryId};
}
}
}
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.
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
BookIdandCategoryIdin our case.
For more information about Entities with Composite Keys, you can read the relavant section from Entites documentation
-
After defining our entities, now we can open the
BookStore.Domain.Sharedproject and add the relevant constants and enums from there.
Step 2 - (Database Integration)
- Create the Domain Entities ✓
- Database Integration ✓
- Db migration ✓
- app services (category -> crud app service)
- ui

