Zum Inhalt

Beispiel für Beziehungen

Dieses umfassende Beispiel demonstriert, wie Sie Beziehungen in Dynamite ORM definieren und verwenden. Lernen Sie, wie Sie Eins-zu-Viele (HasMany) und Viele-zu-Eins (BelongsTo) Beziehungen erstellen, verschachtelte Includes durchführen, zugehörige Daten filtern und komplexe Datenstrukturen aufbauen.

Inhaltsverzeichnis

Grundlagen von Beziehungen

Dynamite unterstützt zwei Arten von Beziehungen:

  • HasMany - Eins-zu-Viele-Beziehung (Eltern hat mehrere Kinder)
  • BelongsTo - Viele-zu-Eins-Beziehung (Kind gehört zu Eltern)

Schlüsselkonzepte

import { HasMany, BelongsTo, NonAttribute } from "@arcaelas/dynamite";

// Elternmodell (User hat viele Orders)
class User extends Table<User> {
  @HasMany(() => Order, "user_id")
  declare orders: NonAttribute<HasMany<Order>>;
}

// Kindmodell (Order gehört zu User)
class Order extends Table<Order> {
  declare user_id: string; // Fremdschlüssel

  @BelongsTo(() => User, "user_id")
  declare user: NonAttribute<BelongsTo<User>>;
}

Wichtig: - Verwenden Sie den NonAttribute<> Wrapper für Beziehungsfelder (werden nicht in DB gespeichert) - HasMany<T> löst sich zu T[] auf (Array von zugehörigen Datensätzen) - BelongsTo<T> löst sich zu T | null auf (einzelner zugehöriger Datensatz oder null) - Fremdschlüsselfeld muss im Kindmodell vorhanden sein

Eins-zu-Viele (HasMany)

Definieren Sie eine Eins-zu-Viele-Beziehung, bei der ein Elternmodell mehrere zugehörige Kinder hat.

Grundlegendes HasMany-Beispiel

import {
  Table,
  PrimaryKey,
  Default,
  HasMany,
  CreationOptional,
  NonAttribute
} from "@arcaelas/dynamite";

// User-Modell (Eltern)
class User extends Table<User> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare name: string;
  declare email: string;

  // Eins-zu-Viele: User hat viele Posts
  @HasMany(() => Post, "user_id")
  declare posts: NonAttribute<HasMany<Post>>;
}

// Post-Modell (Kind)
class Post extends Table<Post> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare user_id: string; // Fremdschlüssel
  declare title: string;
  declare content: string;
}

HasMany-Beziehungen Laden

// Benutzer mit ihren Posts laden
const users_with_posts = await User.where({}, {
  include: {
    posts: {}
  }
});

users_with_posts.forEach(user => {
  console.log(`${user.name} has ${user.posts.length} posts`);
  user.posts.forEach(post => {
    console.log(`  - ${post.title}`);
  });
});

// Spezifischen Benutzer mit Posts laden
const user = await User.first({ id: "user-123" });
if (user) {
  const user_with_posts = await User.where({ id: user.id }, {
    include: { posts: {} }
  });
  console.log(`Posts: ${user_with_posts[0].posts.length}`);
}

Mehrere HasMany-Beziehungen

Ein Modell kann mehrere Eins-zu-Viele-Beziehungen haben:

class User extends Table<User> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare name: string;

  // User hat viele Posts
  @HasMany(() => Post, "user_id")
  declare posts: NonAttribute<HasMany<Post>>;

  // User hat viele Comments
  @HasMany(() => Comment, "user_id")
  declare comments: NonAttribute<HasMany<Comment>>;

  // User hat viele Orders
  @HasMany(() => Order, "user_id")
  declare orders: NonAttribute<HasMany<Order>>;
}

// Benutzer mit allen Beziehungen laden
const users = await User.where({ id: "user-123" }, {
  include: {
    posts: {},
    comments: {},
    orders: {}
  }
});

const user = users[0];
console.log(`Posts: ${user.posts.length}`);
console.log(`Comments: ${user.comments.length}`);
console.log(`Orders: ${user.orders.length}`);

Viele-zu-Eins (BelongsTo)

Definieren Sie eine Viele-zu-Eins-Beziehung, bei der ein Kindmodell zu einem einzelnen Elternteil gehört.

Grundlegendes BelongsTo-Beispiel

// Post-Modell (Kind)
class Post extends Table<Post> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare user_id: string; // Fremdschlüssel
  declare title: string;
  declare content: string;

  // Viele-zu-Eins: Post gehört zu User
  @BelongsTo(() => User, "user_id")
  declare user: NonAttribute<BelongsTo<User>>;
}

// User-Modell (Eltern)
class User extends Table<User> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare name: string;
  declare email: string;
}

BelongsTo-Beziehungen Laden

// Posts mit ihrem Autor laden
const posts_with_author = await Post.where({}, {
  include: {
    user: {}
  }
});

posts_with_author.forEach(post => {
  console.log(`${post.title} by ${post.user?.name || 'Unknown'}`);
});

// Spezifischen Post mit Autor laden
const post = await Post.first({ id: "post-123" });
if (post) {
  const post_with_author = await Post.where({ id: post.id }, {
    include: { user: {} }
  });
  console.log(`Author: ${post_with_author[0].user?.name}`);
}

Verschachtelte Beziehungen

Laden Sie Beziehungen, die ihre eigenen Beziehungen haben (verschachtelte Includes).

Zweistufige Verschachtelung

// User hat viele Posts, Post hat viele Comments
class User extends Table<User> {
  @PrimaryKey()
  declare id: string;
  declare name: string;

  @HasMany(() => Post, "user_id")
  declare posts: NonAttribute<HasMany<Post>>;
}

class Post extends Table<Post> {
  @PrimaryKey()
  declare id: string;
  declare user_id: string;
  declare title: string;

  @BelongsTo(() => User, "user_id")
  declare user: NonAttribute<BelongsTo<User>>;

  @HasMany(() => Comment, "post_id")
  declare comments: NonAttribute<HasMany<Comment>>;
}

class Comment extends Table<Comment> {
  @PrimaryKey()
  declare id: string;
  declare post_id: string;
  declare content: string;
}

// Benutzer mit Posts und Kommentaren laden
const users = await User.where({}, {
  include: {
    posts: {
      include: {
        comments: {}
      }
    }
  }
});

users.forEach(user => {
  console.log(`${user.name}:`);
  user.posts.forEach(post => {
    console.log(`  ${post.title} (${post.comments.length} comments)`);
    post.comments.forEach(comment => {
      console.log(`    - ${comment.content}`);
    });
  });
});

Gefilterte Beziehungen

Filter, Limits und Sortierung auf zugehörige Daten anwenden.

Zugehörige Datensätze Filtern

// Benutzer nur mit veröffentlichten Posts laden
const users = await User.where({ id: "user-123" }, {
  include: {
    posts: {
      where: { status: "published" }
    }
  }
});

console.log(`Published posts: ${users[0].posts.length}`);

Zugehörige Datensätze Begrenzen

// Benutzer mit 5 neuesten Posts laden
const users = await User.where({ id: "user-123" }, {
  include: {
    posts: {
      limit: 5,
      order: "DESC"
    }
  }
});

console.log(`Recent posts: ${users[0].posts.length}`);

Spezifische Attribute Auswählen

// Posts nur mit Benutzername und E-Mail laden
const posts = await Post.where({}, {
  include: {
    user: {
      attributes: ["id", "name", "email"]
    }
  }
});

posts.forEach(post => {
  console.log(`${post.title} by ${post.user?.name} (${post.user?.email})`);
});

Vollständiges E-Commerce-Beispiel

Hier ist ein vollständiges E-Commerce-System, das alle Beziehungsmuster demonstriert:

import {
  Table,
  PrimaryKey,
  Default,
  HasMany,
  BelongsTo,
  CreatedAt,
  UpdatedAt,
  CreationOptional,
  NonAttribute,
  Dynamite
} from "@arcaelas/dynamite";

// User-Modell
class User extends Table<User> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare name: string;
  declare email: string;

  @CreatedAt()
  declare created_at: CreationOptional<string>;

  // Beziehungen
  @HasMany(() => Order, "user_id")
  declare orders: NonAttribute<HasMany<Order>>;
}

// Product-Modell
class Product extends Table<Product> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare name: string;
  declare price: number;

  @CreatedAt()
  declare created_at: CreationOptional<string>;

  // Beziehungen
  @HasMany(() => OrderItem, "product_id")
  declare order_items: NonAttribute<HasMany<OrderItem>>;
}

// Order-Modell
class Order extends Table<Order> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare user_id: string;
  declare total: number;

  @CreatedAt()
  declare created_at: CreationOptional<string>;

  // Beziehungen
  @BelongsTo(() => User, "user_id")
  declare user: NonAttribute<BelongsTo<User>>;

  @HasMany(() => OrderItem, "order_id")
  declare items: NonAttribute<HasMany<OrderItem>>;
}

// OrderItem-Modell
class OrderItem extends Table<OrderItem> {
  @PrimaryKey()
  @Default(() => crypto.randomUUID())
  declare id: CreationOptional<string>;

  declare order_id: string;
  declare product_id: string;
  declare quantity: number;
  declare price: number;

  // Beziehungen
  @BelongsTo(() => Order, "order_id")
  declare order: NonAttribute<BelongsTo<Order>>;

  @BelongsTo(() => Product, "product_id")
  declare product: NonAttribute<BelongsTo<Product>>;
}

// DynamoDB konfigurieren und alle Tabellen registrieren
const dynamite = new Dynamite({
  region: "us-east-1",
  endpoint: "http://localhost:8000",
  tables: [User, Product, Order, OrderItem],
  credentials: {
    accessKeyId: "test",
    secretAccessKey: "test"
  }
});

// Hauptanwendung
async function main() {
  // Verbindung herstellen und Tabellen synchronisieren
  dynamite.connect();
  await dynamite.sync();
  console.log("=== E-Commerce Relationships Example ===\n");

  // Benutzer erstellen
  const user1 = await User.create({
    name: "John Doe",
    email: "john@example.com"
  });

  // Produkte erstellen
  const product1 = await Product.create({
    name: "Laptop",
    price: 999.99
  });

  // Bestellung erstellen
  const order1 = await Order.create({
    user_id: user1.id,
    total: 999.99
  });

  // Bestellartikel erstellen
  await OrderItem.create({
    order_id: order1.id,
    product_id: product1.id,
    quantity: 1,
    price: 999.99
  });

  // Benutzer mit Bestellungen laden
  const users_with_orders = await User.where({ id: user1.id }, {
    include: {
      orders: {}
    }
  });
  console.log(`${users_with_orders[0].name} has ${users_with_orders[0].orders.length} order(s)`);

  // Bestellung mit Artikeln und Produkten laden
  const orders_with_items = await Order.where({ id: order1.id }, {
    include: {
      user: {},
      items: {
        include: {
          product: {}
        }
      }
    }
  });
  console.log(`Order ${orders_with_items[0].id} by ${orders_with_items[0].user?.name}`);

  console.log("=== All relationship operations completed ===");
}

// Anwendung ausführen
main().catch(console.error);

Erwartete Ausgabe

=== E-Commerce Relationships Example ===

John Doe has 1 order(s)
Order 550e8400-... by John Doe

=== All relationship operations completed ===

Erweiterte Muster

Selbstreferenzielle Beziehungen

Modelle können Beziehungen zu sich selbst haben:

class Category extends Table<Category> {
  @PrimaryKey()
  declare id: string;

  declare name: string;
  declare parent_id: string | null;

  // Category hat viele Kindkategorien
  @HasMany(() => Category, "parent_id")
  declare children: NonAttribute<HasMany<Category>>;

  // Category gehört zu Elternkategorie
  @BelongsTo(() => Category, "parent_id")
  declare parent: NonAttribute<BelongsTo<Category>>;
}

Viele-zu-Viele-Beziehungen (über Verbindungstabelle)

Viele-zu-Viele mit Verbindungstabelle implementieren:

// Student-Modell
class Student extends Table<Student> {
  @PrimaryKey()
  declare id: string;
  declare name: string;

  @HasMany(() => Enrollment, "student_id")
  declare enrollments: NonAttribute<HasMany<Enrollment>>;
}

// Course-Modell
class Course extends Table<Course> {
  @PrimaryKey()
  declare id: string;
  declare name: string;

  @HasMany(() => Enrollment, "course_id")
  declare enrollments: NonAttribute<HasMany<Enrollment>>;
}

// Verbindungstabelle
class Enrollment extends Table<Enrollment> {
  @PrimaryKey()
  declare id: string;

  declare student_id: string;
  declare course_id: string;
  declare grade: string;

  @BelongsTo(() => Student, "student_id")
  declare student: NonAttribute<BelongsTo<Student>>;

  @BelongsTo(() => Course, "course_id")
  declare course: NonAttribute<BelongsTo<Course>>;
}

Best Practices

1. NonAttribute für Beziehungen Verwenden

// Gut - als NonAttribute markiert
@HasMany(() => Order, "user_id")
declare orders: NonAttribute<HasMany<Order>>;

// Schlecht - wird versuchen, in Datenbank zu speichern
@HasMany(() => Order, "user_id")
declare orders: Order[];

2. Fremdschlüssel Explizit Definieren

// Gut - expliziter Fremdschlüssel
class Order extends Table<Order> {
  declare user_id: string; // Fremdschlüsselfeld

  @BelongsTo(() => User, "user_id")
  declare user: NonAttribute<BelongsTo<User>>;
}

3. Pfeilfunktionen in Decorators Verwenden

// Gut - Pfeilfunktion (vermeidet Zirkelbezug)
@HasMany(() => Order, "user_id")
declare orders: NonAttribute<HasMany<Order>>;

4. Beziehungen für Performance Filtern

// Gut - nur laden, was benötigt wird
const users = await User.where({}, {
  include: {
    orders: {
      where: { status: "completed" },
      limit: 10,
      attributes: ["id", "total"]
    }
  }
});

5. N+1-Abfragen Vermeiden

// Gut - Beziehungen in einer Abfrage laden
const posts = await Post.where({}, {
  include: {
    user: {},
    comments: {}
  }
});

Nächste Schritte

Verwandte Dokumentation

Viel Erfolg beim Programmieren mit Dynamite-Beziehungen!