# 模型

Cotton 提供了一种类型安全的方式来使用模型处理数据库。 你可以通过创建一个装饰有以下内容的普通类来定义模型:@Model

import { Model, Column } from "https://deno.land/x/cotton/mod.ts";

@Model("users")
class User {
  @Primary()
  id!: number;

  @Column()
  email!: string;

  @Column()
  age!: number;

  @Column()
  createdAt!: Date;
}

@Model 装饰器接受一个可选参数作为模型的表名。 默认情况下,如果类名是 User,它将在数据库中查找 user 表。

模型必须具有主列。 在这点上,Cotton 仅支持自动增量主键。

然后,通过用 @Column 标记类属性来定义每列。 Cotton 足够聪明,可以使用 TypeScript 类型来确定该特定列的数据类型。 但是,你仍然可以通过传递type选项来自定义列类型,如下所示。

@Column({ type: DataType.String })
email!: string;

仍有大量定制空间。 你可以使用 default 提供列的默认值,并使用 name 为列定义自定义名称,等等。

@Column({ default: false })
isActive!: string;

@Column({ default: () => new Date() })
createdAt!: Date;

@Column({ name: "created_at" }) // different column name on the database
createdAt!: Date;

# TypeScript 配置

请记住,此功能需要自定义 TypeScript 配置,以告知 Deno 我们要使用TypeScript decorators (opens new window),该功能目前仍是实验性功能。

// tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

# 模型管理器

为了在模型中执行查询,你可以使用数据库连接实例提供的模型管理器。

const db = await connect({
  type: "sqlite",
  // other options...
});

const manager = db.getManager();

一旦你获得模型管理器实例,你可以在模型上执行任何操作。 让我们来以创建一个用户开始。

const user = new User();
user.email = "a@b.com";
user.age = 16;
user.createdAt = new Date();
await manager.save(user);

为了更新已经存在的模型,你可以再次执行 save 操作。Cotton 会智能的决定是要更新还是插入数据。

user.email = "b@c.com";
await manager.save(user);

你也可以给 save 通过传入一个数组来执行多条数据的插入和更新。

const user1 = new User();
user1.email = "a@b.com";
await manager.save(user1);
user1.email = "b@c.com";

const user2 = new User();
user2.email = "a@b.com";

const post1 = new Post();
post1.title = "Spoon";

await manager.save([
  user1, // Since it's already saved, it will perform update.
  user2, // This record will inserted.
  post1, // You can also save a totally different model at once!
]);

# 查询模型

任何你想查询的内容都可以通过 query 来获取。

const users = await manager.query(User).all();

for (const user of users) {
  console.log(user); // User { email: 'a@b.com', age: 16, ... }
}

query 方法返回 ModelQuery 的实例,该实例的工作方式类似于查询生成器。 你还可以通过将记录与 whereornot 连接起来,在一定条件下过滤记录。

await manager.query(User).where("email", "a@b.com").all();

如果要获取第一个,可以用 first代替 all

const user = await manager.query(User).where("email", "a@b.com").first();

console.log(user); // User { email: 'a@b.com', age: 16, ... }

# 计数

你可以通过 count 方法统计到满足条件的数据。

const count = await manager.query(User).where("isActive", true).count();

# 更新模型

要一次更新多个模型,可以使用 update 方法并传递数据。 这不会返回更新的模型,而是使用 save

await manager.query(User).where("isActive", true).update({ isActive: false });

# 删除模型

Manager API 具有 remove 方法,使你可以删除从数据库中的模型。

await manager.remove(user);

你还可以通过传递数组作为参数来删除多个模型。 这将在单个查询中删除所有模型。

await manager.remove([user1, user2, user3]);

要删除符合给定条件的模型,你可以使用 delete 方法删除。

await manager.query(User).where("isActive", false).delete();

# 关系

Cotton 使你可以轻松设置模型关系。 当前,Cotton 仅支持一对多关系。

@Model()
class User {
  @Primary()
  id!: number;

  @Column()
  email: string;

  @HasMany(() => User, "user_id")
  posts: Post[];
}

@Model()
class Post {
  @Primary()
  id!: number;

  @Column()
  title: string;

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

如你所见,一个用户可以有多个帖子,但是一个帖子属于一个用户。

# 保存关系

保存关系可以单独使用 save 方法来完成。

const post1 = new Post();
post1.title = "Post 1";
await manager.save(post1);

const post2 = new Post();
post2.title = "Post 2";
await manager.save(post2);

const user = new User();
user.email = "a@b.com";
user.posts = [post1, post2];
await manager.save(user);

你也可以将其反转。

const user = new User();
user.email = "a@b.com";
await manager.save(user);

const post1 = new Post();
post1.title = "Post 1";
post1.user = user;
await manager.save(post1);

const post2 = new Post();
post2.title = "Post 1";
post2.user = user;
await manager.save(post2);

# 获取关系

默认情况下,findfindOne 不会获取你的关系。 要获取它们,你需要明确说明要包含的关系。

const users = await manager.query(User).include("posts").all();
const post = await manager.query(Post).include("user").first();

# 基础模型

如果发现难以使用模型管理器,则基本模型可能是你的理想解决方案。

简而言之,基础模型是扩展 BaseModel类的模型。 该类为你提供与模型管理器完全相同的功能,但是你可以直接从模型类中调用它们。

@Model("users")
class User extends BaseModel {
  @Column()
  email!: string;

  @Column()
  age: number;

  @Column()
  createdAt!: Date;
}

使用基本模型时,你需要做的最重要的事情是将这些模型注册到数据库连接中。 在执行查询之前未注册模型会导致致命错误。

const db = await connect({
  type: "sqlite",
  // 其他配置...
  models: [User],
});

这是对基本模型执行查询的方法。

const user = await User.query().where("id", 1).first();

插入新的条目:

const user = new User();
user.email = "a@b.com";
user.age = 16;
user.createdAt = new Date();
await user.save();

// 或者...
const user = await User.insert({
  email: "a@b.com",
  age: 16,
  createdAt: new Date(),
});

移除一个条目:

const user = await User.query().where("id", 1).first();
await user.remove();

# 他们之间的区别

模型管理器和基本模型之间最明显的区别是模型管理器与模型无关。 这意味着它将与你拥有的任何模型一起使用。

在模型管理器中,你的模型只是一个普通类,仅充当数据库中表模式的表示。 为了与数据库进行交互,你需要使用模型管理器。 某些人发现它比基本模型更安全,因为它将业务逻辑和模式定义分开。 你可以在Java框架, 例如Hibernate (opens new window) 中看到很多这种模式。

另一方面,基于模型,你的模型既充当模型管理器又充当架构。 你可以在 Laravel's Eloquent (opens new window)ActiveRecord (opens new window) 中看到这种模式。 许多人发现此模式更易于使用,因为一旦访问了模型类,就可以使用它进行任何操作。

# 我应该用哪一个?

完全取决于你! 我个人认为这取决于你来自哪里。 如果你来自 Java 开发,并且已经熟悉 JPA ,则可能要使用模型管理器。 但是,如果你来自 PHP、Ruby 或 Node.js,则基本模型对你来说似乎更自然。