简介
在我学习 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 的陷阱:
- 无自动状态跟踪 :不同于 ORM,Ecto 不会跟踪实体状态(如脏字段或修改字段)。
- 显式数据流 :开发者必须手动通过变更集传递数据后才能持久化。
- 函数式编程范式 :Ecto 与 Elixir 的函数式编程模型一致,避免对象-关系映射的“阻抗失配”。
要求
- Elixir 1.18+ (根据需要调整旧版本)
mix.exs中的依赖:
{:ecto_sql, "~> 3.0"}, # Ecto
{:postgrex, ">= 0.0.0"} # PostgreSQL驱动
设置仓库(Repository)
仓库(或 Repo)是 Ecto 与数据库交互的接口,类似于 Entity Framework 的 DbContext。
手动设置
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/<app_name>/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/<datetime>_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_many、belongs_to 和 many_to_many 的支持。
448

被折叠的 条评论
为什么被折叠?



