Ecto 与 Elixir 集成:数据库交互入门指南

简介

在我学习 Elixir 的过程中,我发现了 Ecto ,这是一个强大的数据库封装器和查询生成器,能够无缝对接 SQL 数据库。尽管常与 Entity Framework Core 或 Ruby on Rails 的 ActiveRecord 等 ORM 框架对比,但 Ecto 的设计不同:它不自动跟踪状态,要求开发者显式管理数据变化。本文将探讨 Ecto 的核心概念,包括仓库(Repository)、模式(Schema)、迁移(Migration)和基础增删改查(CRUD)操作。高级功能请参考 Ecto 官方文档 。

什么是 Ecto?

Ecto 是 Elixir 的数据库封装器和查询生成器,专为 PostgreSQL、MySQL 等关系型数据库设计。它提供以下功能:

  • 映射数据库表到 Elixir 结构体(通过 Schema)
  • 使用 Elixir 语法生成类型安全的查询
  • 通过变更集(Changeset)在持久化前验证数据
  • 通过版本控制的迁移管理数据库模式演进

为什么 Ecto 不是传统 ORM

Ecto 通过以下方式避免传统 ORM 的陷阱:

  1. 无自动状态跟踪 :不同于 ORM,Ecto 不会跟踪实体状态(如脏字段或修改字段)。
  2. 显式数据流 :开发者必须手动通过变更集传递数据后才能持久化。
  3. 函数式编程范式 :Ecto 与 Elixir 的函数式编程模型一致,避免对象-关系映射的“阻抗失配”。

要求

  • Elixir 1.18+ (根据需要调整旧版本)
  • mix.exs 中的依赖:
{:ecto_sql, "~> 3.0"},  # Ecto
{:postgrex, ">= 0.0.0"} # PostgreSQL驱动

设置仓库(Repository)

仓库(或 Repo)是 Ecto 与数据库交互的接口,类似于 Entity FrameworkDbContext

手动设置

1. 定义仓库模块:

defmodule Friends.Repo do
  use Ecto.Repo,
      otp_app: :friend,
      adapter: Ecto.Adapters.Postgres
end

2. 在 config/config.exs 中配置:

config :friends, Friends.Repo,
  database: "friends",
  username: "user",
  password: "pass",
  hostname: "localhost"

通过 Mix 任务自动设置

运行:

mix ecto.gen.repo -r Friends.Repo

启动时运行 Ecto

lib//application.ex 的应用监督者中添加仓库:

def start(_type, _args) do
  children = [Friends.Repo]
  ...
end

然后更新 config/config.exs

config :friends, ecto_repos: [Friends.Repo]

创建模式(Schema)

模式将数据库表映射为 Elixir 结构体。例如,定义一个 Person 模式:

defmodule Friends.Person do
  use Ecto.Schema
  import Ecto.Changeset

  schema "people" do
    field :first_name, :string
    field :last_name, :string
    field :age, :integer
  end

  def changeset(person, params \\ %{}) do
    person
    |> cast(params, [:first_name, :last_name, :age])
    |> validate_required([:first_name, :last_name])
  end
end

迁移(Migration)

迁移以增量方式定义数据库模式变更。

手动迁移

priv/repo/migrations/_create_people.exs 创建文件:

defmodule Friends.Repo.Migrations.CreatePeople do
  use Ecto.Migration

  def change do
    create table(:people) do
      add :first_name, :string
      add :last_name, :string
      add :age, :integer
    end
  end
end

通过 Mix 任务自动生成迁移

运行:

mix ecto.gen.migration create_people

该命令会创建一个空迁移文件:

defmodule Friends.Repo.Migrations.CreatePeople do
  use Ecto.Migration

  def change do

  end
end

执行迁移

运行:

mix ecto.create # 创建数据库
mix ecto.migrate # 执行迁移

增删改查(CRUD)操作

创建(Create)

插入新记录:

person = %Friends.Person{first_name: "Alice", last_name: "Smith", age: 30}
{:ok, inserted_person} = Friends.Repo.insert(person)

带验证的插入:

changeset = Friends.Person.changeset(%Friends.Person{}, %{first_name: "Alice"})
case Friends.Repo.insert(changeset) do
  {:ok, person} -> # 成功
  {:error, changeset} -> # 处理错误
end

读取(Read)

获取记录:

# 通过 ID 获取
Friends.Repo.get(Friends.Person, 1)

# 获取第一条记录
Friends.Repo.one(from p in Friends.Person, order_by: [asc: p.id], limit: 1)

# 获取符合条件的所有记录
Friends.Repo.all(from p in Friends.Person, where: like(p.first_name, "A%"))

更新(Update)

更新现有记录:

person = Friends.Repo.get!(Friends.Person, 1)
changeset = Friends.Person.changeset(person, %{age: 31})
Friends.Repo.update(changeset)

删除(Delete)

删除记录:

person = Friends.Repo.get!(Friends.Person, 1)
Friends.Repo.delete(person)

结论

Ecto 在抽象与控制之间取得平衡,提供以下优势:

  • 类型安全的查询 :编译时宏生成的查询可避免运行时错误。
  • 显式工作流 :变更集强制在持久化前验证数据。
  • 社区支持 :在 Elixir 生态系统中广泛采用。

尽管不是传统 ORM,Ecto 的函数式设计避免了常见的对象-关系映射抽象漏洞。对于高级模式(如关联关系),可探索其对 has_manybelongs_tomany_to_many 的支持。

参考

  • Ecto 官方文档

你可能感兴趣的:(elixir,erlang,ecto)