В контексте приложения Todo отношение «один ко многим» можно проиллюстрировать следующим образом:
У одного TodoList
может быть много TodoItem
.
Например, рассмотрим приложение Todo, в котором пользователь может создать несколько списков Todo, каждый из которых содержит набор элементов Todo. Каждый список задач однозначно идентифицируется идентификатором, и каждый элемент задачи связан со списком задач посредством отношения внешнего ключа.
В этом сценарии мы можем определить класс сущности TodoList
с первичным ключом id
и свойством name
, представляющим имя списка. Мы также можем определить класс сущностей TodoItem
с первичным ключом id
, свойством description
, представляющим описание задачи, и внешним ключом todoListId
, ссылающимся на id
из TodoList
.
@Entity() export class TodoList { @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToMany(() => TodoItem, todoItem => todoItem.todoList) todoItems: TodoItem[]; } @Entity() export class TodoItem { @PrimaryGeneratedColumn() id: number; @Column() description: string; @ManyToOne(() => TodoList, todoList => todoList.todoItems) todoList: TodoList; @Column() todoListId: number; }
В нашем предыдущем примере мы использовали декораторы @OneToMany и @ManyToOne, предоставленные TypeORM, для установления отношения «один ко многим» между сущностями TodoList и TodoItem. Декоратор @OneToMany использовался для определения обратной стороны отношения в классе TodoList, а декоратор @ManyToOne определял владеющую сторону отношения в классе TodoItem.
Определив обе стороны связи, мы можем обеспечить лучшую согласованность данных, сделать запросы к данным более интуитивными и улучшить организацию кода. С помощью этой настройки мы можем легко получить все объекты TodoItem, связанные с конкретным объектом TodoList, запросив свойство todoItems объекта TodoList.
Давайте сосредоточимся на декораторе @OneToMany, который используется для определения отношения «один ко многим» между двумя объектами в TypeORM. Синтаксис этого декоратора:
@OneToMany(type => RelatedEntity, relatedEntity => relatedEntity.entityProperty)
Часть «type =› RelatedEntity» указывает тип связанной сущности, которая определяет «многие» стороны отношения. В нашем случае связанной сущностью является TodoItem. Часть «relatedEntity =› relatedEntity.entityProperty» указывает свойство в связанном объекте, которое представляет «одну» сторону отношения. В нашем примере это свойство todoList, представляющее сущность TodoList, которая имеет множество сущностей TodoItem.
Используя этот декоратор, TypeORM может отображать две сущности и устанавливать отношения между ними. В результате мы можем легко извлекать связанные данные и обеспечивать лучшую согласованность данных, делая наш код более организованным и простым в обслуживании.
Теперь, когда мы определили наши сущности TodoList и TodoItem, мы можем приступить к реализации функциональности CRUD с отношением «один ко многим». Следующим шагом является определение нашего TodoListService, который будет содержать методы CRUD:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { TodoList } from './entities/todo-list.entity'; @Injectable() export class TodoListService { constructor( @InjectRepository(TodoList) private readonly todoListRepository: Repository<TodoList>, ) {} async findAll(): Promise<TodoList[]> { return this.todoListRepository.find(); } async create(name: string, items: TodoItem[]): Promise<TodoList> { const todoList = new TodoList(); todoList.name = name; todoList.todoItems = items; return this.todoListRepository.save(todoList); } async update(id: number, updateTodoListDto: UpdateTodoListDto): Promise<TodoList> { const todoList = await this.todoListRepository.findOne(id, { relations: ['todoItems'] }); if (!todoList) { throw new NotFoundException(`Todo list with id ${id} not found`); } const { name, todoItems } = updateTodoListDto; if (name) { todoList.name = name; } if (todoItems) { // Remove existing todo items todoList.todoItems = []; // Create new todo items and associate them with the todo list for (const todoItemDto of todoItems) { const todoItem = new TodoItem(); todoItem.name = todoItemDto.name; todoItem.done = todoItemDto.done; todoItem.todoList = todoList; await this.todoItemRepository.save(todoItem); todoList.todoItems.push(todoItem); } } return this.todoListRepository.save(todoList); } async delete(id: number): Promise<void> { await this.todoListRepository.delete(id); } }
Метод findAll
извлекает все TodoList
объектов. Метод create
создает новый объект TodoList
с заданным именем и списком объектов TodoItem
. Метод update
обновляет существующий объект TodoList
с заданным идентификатором и свойствами, указанными в объекте UpdateTodoListDto
, включая список связанных объектов TodoItem
. Метод delete
удаляет существующий объект TodoList
с заданным идентификатором.
Наконец, мы можем определить наш TodoListController
для обработки HTTP-запросов:
import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common'; import { TodoListService } from './todo-list.service'; import { CreateTodoListDto } from './dto/create-todo-list.dto'; import { UpdateTodoListDto } from './dto/update-todo-list.dto'; import { TodoList } from './entities/todo-list.entity'; @Controller('todo-lists') export class TodoListController { constructor(private readonly todoListService: TodoListService) {} @Get() async findAll(): Promise<TodoList[]> { return this.todoListService.findAll(); } @Post() async create(@Body() createTodoListDto: CreateTodoListDto): Promise<TodoList> { return this.todoListService.create(createTodoListDto.name, createTodoListDto.todoItems); } @Put(':id') async update( @Param('id') id: number, @Body() updateTodoListDto: UpdateTodoListDto, ): Promise<TodoList> { return this.todoListService.update(id, updateTodoListDto); } @Delete(':id') async delete(@Param('id') id: number): Promise<void> { return this.todoListService.delete(id); } }
Мы определили контроллер API с четырьмя конечными точками, которые соответствуют операциям CRUD, определенным в файле TodoListService
. Декоратор @Get
используется для метода findAll
, декоратор @Post
для create
, декоратор @Put
для update
и декоратор @Delete
для delete
. Декораторы @Body
и @Param
используются для извлечения данных из тела запроса и параметров URL соответственно.
Обратите внимание, что контроллер зависит от экземпляра класса TodoListService
, который передается конструктору с помощью внедрения зависимостей.
В заключение, связь «один ко многим» в TypeORM и NestJS может значительно улучшить функциональность вашего приложения, позволяя вам создавать более сложную и всеобъемлющую модель данных. Поняв, как реализовать это отношение и связанные с ним декораторы, вы сможете создавать более надежные и эффективные приложения. Поэтому не стесняйтесь экспериментировать с отношениями «один ко многим» в своем следующем проекте!
Если вы найдете эту информацию полезной, поддержите меня и следите за обновлениями.🙂