Перейти к основному содержимому

Связь (Relation)

Поле типа Связь позволяет создавать связи между записями разных коллекций.

Основные характеристики

  • Тип данных: Ссылка на запись в другой коллекции
  • Режимы: Одна запись (One-to-One) или множество записей (One-to-Many)
  • Обратная связь: Автоматическое создание обратного поля
  • Каскадное удаление: Настраиваемое поведение при удалении

Типы связей

One-to-One (Один к одному)

Одна запись связана с одной записью в другой коллекции.

{
"name": "profile",
"type": "relation",
"collection": "profiles",
"multiple": false
}

Пример: Пользователь → Профиль

One-to-Many (Один ко многим)

Одна запись связана с множеством записей в другой коллекции.

{
"name": "orders",
"type": "relation",
"collection": "orders",
"multiple": true
}

Пример: Клиент → Заказы

Many-to-Many (Многие ко многим)

Множество записей связаны с множеством записей.

{
"name": "tags",
"type": "relation",
"collection": "tags",
"multiple": true,
"backReference": {
"field": "articles",
"multiple": true
}
}

Пример: Статьи ↔ Теги

Настройки поля

При создании поля типа "Связь" доступны следующие настройки:

  • Название поля: Отображаемое имя поля
  • Системное имя: Уникальный идентификатор для API
  • Описание: Подсказка для пользователей
  • Связанная коллекция: Коллекция для связи
  • Множественный выбор: Разрешить выбор нескольких записей
  • Обратная связь: Создать обратное поле в связанной коллекции
  • Каскадное удаление: Удалять связанные записи при удалении
  • Обязательное поле: Требовать выбора записи

Примеры использования

Автор статьи (One-to-One)

{
"name": "author",
"type": "relation",
"collection": "users",
"multiple": false,
"required": true
}

Заказы клиента (One-to-Many)

{
"name": "orders",
"type": "relation",
"collection": "orders",
"multiple": true,
"backReference": {
"field": "customer",
"multiple": false
}
}

Категория товара

{
"name": "category",
"type": "relation",
"collection": "categories",
"multiple": false,
"required": true
}

Теги статьи (Many-to-Many)

{
"name": "tags",
"type": "relation",
"collection": "tags",
"multiple": true,
"backReference": {
"field": "articles",
"multiple": true
}
}

Работа через API

Создание записи со связью

// Создать статью с автором
const article = await emd.database.collection('articles').create({
title: 'Моя статья',
author: 'user_123' // ID пользователя
});

// Создать заказ с товарами
const order = await emd.database.collection('orders').create({
customer: 'customer_456',
items: ['product_1', 'product_2', 'product_3']
});

Получение связанных данных

const article = await emd.database.collection('articles').get(articleId);

console.log(article.author);
// {
// id: "user_123",
// name: "Иван Иванов",
// email: "ivan@example.com"
// }

Получение с populate (развертывание связей)

const article = await emd.database.collection('articles')
.get(articleId, {
populate: ['author', 'category', 'tags']
});

console.log(article.author.name);
console.log(article.category.title);
console.log(article.tags.map(t => t.name));

Обновление связи

// Изменить автора
await emd.database.collection('articles').update(articleId, {
author: 'user_789'
});

// Добавить тег
const article = await emd.database.collection('articles').get(articleId);
await emd.database.collection('articles').update(articleId, {
tags: [...article.tags.map(t => t.id), 'tag_new']
});

Удаление связи

// Удалить связь (установить null)
await emd.database.collection('articles').update(articleId, {
author: null
});

// Удалить из массива связей
const article = await emd.database.collection('articles').get(articleId);
const updatedTags = article.tags
.filter(t => t.id !== 'tag_to_remove')
.map(t => t.id);

await emd.database.collection('articles').update(articleId, {
tags: updatedTags
});

Поиск по связям

Поиск по связанной записи

// Статьи определенного автора
const articles = await emd.database.collection('articles').find({
author: 'user_123'
});

// Товары определенной категории
const products = await emd.database.collection('products').find({
category: 'category_456'
});

Поиск по множественным связям

// Статьи с определенным тегом
const articles = await emd.database.collection('articles').find({
tags: { $contains: 'tag_javascript' }
});

// Статьи с любым из тегов
const articles = await emd.database.collection('articles').find({
tags: { $in: ['tag_js', 'tag_react', 'tag_vue'] }
});

// Статьи со всеми указанными тегами
const articles = await emd.database.collection('articles').find({
tags: { $all: ['tag_js', 'tag_tutorial'] }
});

Поиск по полям связанной записи

// Статьи автора с определенным email
const articles = await emd.database.collection('articles').find({
'author.email': 'ivan@example.com'
});

// Товары категории с определенным названием
const products = await emd.database.collection('products').find({
'category.name': 'Электроника'
});

Обратные связи

Автоматическое создание обратного поля

{
"collection": "articles",
"field": {
"name": "author",
"type": "relation",
"collection": "users",
"backReference": {
"field": "articles",
"multiple": true
}
}
}

Теперь у пользователя автоматически появится поле articles со всеми его статьями.

Доступ к обратной связи

const user = await emd.database.collection('users').get(userId, {
populate: ['articles']
});

console.log(user.articles);
// [
// { id: "article_1", title: "Статья 1" },
// { id: "article_2", title: "Статья 2" }
// ]

Каскадное удаление

Удалить связанные записи

{
"name": "comments",
"type": "relation",
"collection": "comments",
"multiple": true,
"onDelete": "cascade" // Удалить все комментарии при удалении статьи
}

Установить null

{
"name": "author",
"type": "relation",
"collection": "users",
"onDelete": "set_null" // Установить author = null при удалении пользователя
}

Запретить удаление

{
"name": "category",
"type": "relation",
"collection": "categories",
"onDelete": "restrict" // Запретить удаление категории, если есть товары
}

Фильтрация связанных записей

Ограничить выбор

{
"name": "category",
"type": "relation",
"collection": "categories",
"filter": {
"is_active": true,
"type": "product"
}
}

Сортировка связанных записей

{
"name": "comments",
"type": "relation",
"collection": "comments",
"multiple": true,
"sort": {
"created_at": -1
}
}

Вложенные связи

Получение вложенных данных

const order = await emd.database.collection('orders').get(orderId, {
populate: [
'customer',
{
path: 'items',
populate: ['product']
}
]
});

console.log(order.customer.name);
console.log(order.items[0].product.title);

Агрегация по связям

Подсчет связанных записей

const users = await emd.database.collection('users').aggregate([
{
$lookup: {
from: 'articles',
localField: '_id',
foreignField: 'author',
as: 'articles'
}
},
{
$addFields: {
articlesCount: { $size: '$articles' }
}
}
]);

Группировка по связанной записи

const stats = await emd.database.collection('articles').aggregate([
{
$group: {
_id: '$category',
count: { $sum: 1 }
}
}
]);

Производительность

Индексация

// Создать индекс для быстрого поиска
await emd.database.collection('articles').createIndex({
author: 1
});

Ленивая загрузка

// Получить только ID, без populate
const article = await emd.database.collection('articles').get(articleId);
console.log(article.author); // "user_123" (только ID)

// Загрузить данные автора по требованию
const author = await emd.database.collection('users').get(article.author);

Пагинация связанных записей

const user = await emd.database.collection('users').get(userId);

const articles = await emd.database.collection('articles')
.find({ author: userId })
.limit(10)
.skip(20);

Рекомендации

  • Используйте связи вместо дублирования данных
  • Создавайте индексы для полей связей
  • Используйте populate только когда необходимо
  • Для больших коллекций используйте пагинацию
  • Настраивайте каскадное удаление осторожно
  • Используйте обратные связи для удобства
  • Документируйте структуру связей

Отличие от других типов

  • Relation vs User: Relation для любых коллекций, User специфичен для пользователей
  • Relation vs ObjectId: Relation предоставляет полные данные записи, ObjectId только ID
  • Relation vs JSON: Relation для связей между коллекциями, JSON для произвольных данных