Ecto简介 #
一、Ecto概述 #
1.1 什么是Ecto #
Ecto是Elixir的官方数据库抽象层和查询语言。它提供了:
- Repository: 数据库连接和操作
- Schema: 数据模型定义
- Changeset: 数据验证和转换
- Query: 类型安全的查询语言
1.2 Ecto架构 #
text
Ecto架构
├── Repository
│ ├── 数据库连接
│ ├── 事务管理
│ └── 基础CRUD
├── Schema
│ ├── 数据模型
│ ├── 字段定义
│ └── 关联关系
├── Changeset
│ ├── 数据验证
│ ├── 类型转换
│ └── 错误处理
└── Query
├── 查询构建
├── 条件过滤
└── 关联预加载
二、Repository #
2.1 定义Repository #
elixir
defmodule Hello.Repo do
use Ecto.Repo,
otp_app: :hello,
adapter: Ecto.Adapters.Postgres
end
2.2 配置连接 #
elixir
config :hello, Hello.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "hello_dev",
pool_size: 10
2.3 启动Repository #
elixir
defmodule Hello.Application do
use Application
def start(_type, _args) do
children = [
Hello.Repo
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
三、基础操作 #
3.1 插入数据 #
elixir
def create_user(attrs \\ %{}) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
end
3.2 查询数据 #
elixir
def list_users do
Repo.all(User)
end
def get_user!(id), do: Repo.get!(User, id)
def get_user(id), do: Repo.get(User, id)
def get_user_by_email(email) do
Repo.get_by(User, email: email)
end
3.3 更新数据 #
elixir
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
end
3.4 删除数据 #
elixir
def delete_user(%User{} = user) do
Repo.delete(user)
end
四、Schema #
4.1 定义Schema #
elixir
defmodule Hello.Accounts.User do
use Ecto.Schema
schema "users" do
field :email, :string
field :name, :string
field :age, :integer
field :active, :boolean, default: true
field :bio, :string
timestamps()
end
end
4.2 字段类型 #
| 类型 | 说明 |
|---|---|
| :integer | 整数 |
| :float | 浮点数 |
| :boolean | 布尔值 |
| :string | 字符串 |
| :binary | 二进制 |
| :date | 日期 |
| :time | 时间 |
| :naive_datetime | 日期时间(无时区) |
| :utc_datetime | 日期时间(UTC) |
| :map | 映射 |
| 数组 |
4.3 主键 #
elixir
schema "users" do
field :id, :binary_id, primary_key: true
field :email, :string
end
4.4 自定义主键 #
elixir
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "users" do
field :email, :string
end
五、Changeset #
5.1 定义Changeset #
elixir
defmodule Hello.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :name, :string
field :password, :string, virtual: true
field :hashed_password, :string
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :name])
|> validate_required([:email, :name])
|> validate_format(:email, ~r/@/)
|> unique_constraint(:email)
end
def registration_changeset(user, attrs) do
user
|> changeset(attrs)
|> cast(attrs, [:password])
|> validate_required([:password])
|> validate_length(:password, min: 8)
|> put_hashed_password()
end
defp put_hashed_password(changeset) do
case get_change(changeset, :password) do
nil -> changeset
password -> put_change(changeset, :hashed_password, hash_password(password))
end
end
end
5.2 cast函数 #
elixir
cast(struct, params, allowed_fields)
- struct: Schema结构体
- params: 参数映射
- allowed_fields: 允许更新的字段列表
5.3 验证函数 #
elixir
changeset
|> validate_required([:email, :name])
|> validate_format(:email, ~r/@/)
|> validate_length(:name, min: 2, max: 100)
|> validate_number(:age, greater_than: 0)
|> validate_inclusion(:status, ["active", "inactive"])
|> validate_exclusion(:role, ["admin"])
|> unique_constraint(:email)
|> foreign_key_constraint(:post_id)
六、Query基础 #
6.1 基本查询 #
elixir
import Ecto.Query
def list_active_users do
from(u in User, where: u.active == true)
|> Repo.all()
end
def list_users_ordered do
from(u in User, order_by: [desc: u.inserted_at])
|> Repo.all()
end
6.2 条件查询 #
elixir
def search_users(name) do
from(u in User, where: ilike(u.name, ^"%#{name}%"))
|> Repo.all()
end
def get_users_by_age(min, max) do
from(u in User, where: u.age >= ^min and u.age <= ^max)
|> Repo.all()
end
6.3 选择字段 #
elixir
def list_user_names do
from(u in User, select: u.name)
|> Repo.all()
end
def list_user_summaries do
from(u in User, select: %{id: u.id, name: u.name, email: u.email})
|> Repo.all()
end
6.4 分页 #
elixir
def list_users_paginated(page, per_page) do
offset = (page - 1) * per_page
from(u in User,
order_by: [desc: u.inserted_at],
limit: ^per_page,
offset: ^offset
)
|> Repo.all()
end
七、事务 #
7.1 基本事务 #
elixir
def transfer_money(from_id, to_id, amount) do
Repo.transaction(fn ->
from_account = Repo.get!(Account, from_id)
to_account = Repo.get!(Account, to_id)
{:ok, _} = update_balance(from_account, from_account.balance - amount)
{:ok, _} = update_balance(to_account, to_account.balance + amount)
end)
end
7.2 with事务 #
elixir
def create_user_with_profile(attrs) do
Ecto.Multi.new()
|> Ecto.Multi.insert(:user, User.changeset(%User{}, attrs))
|> Ecto.Multi.insert(:profile, fn %{user: user} ->
Profile.changeset(%Profile{user_id: user.id}, attrs)
end)
|> Repo.transaction()
end
7.3 Ecto.Multi #
elixir
def complex_operation(attrs) do
Ecto.Multi.new()
|> Ecto.Multi.insert(:user, User.changeset(%User{}, attrs))
|> Ecto.Multi.run(:account, fn repo, %{user: user} ->
create_account(repo, user)
end)
|> Ecto.Multi.update(:stats, fn %{user: user} ->
update_user_stats(user)
end)
|> Repo.transaction()
|> case do
{:ok, result} -> {:ok, result.user}
{:error, :user, changeset, _} -> {:error, changeset}
{:error, :account, error, _} -> {:error, error}
end
end
八、关联关系 #
8.1 一对多 #
elixir
defmodule Hello.Blog.Post do
use Ecto.Schema
schema "posts" do
field :title, :string
field :body, :string
has_many :comments, Hello.Blog.Comment
end
end
defmodule Hello.Blog.Comment do
use Ecto.Schema
schema "comments" do
field :body, :string
belongs_to :post, Hello.Blog.Post
end
end
8.2 多对多 #
elixir
defmodule Hello.Blog.Post do
use Ecto.Schema
schema "posts" do
field :title, :string
many_to_many :tags, Hello.Blog.Tag, join_through: "posts_tags"
end
end
defmodule Hello.Blog.Tag do
use Ecto.Schema
schema "tags" do
field :name, :string
many_to_many :posts, Hello.Blog.Post, join_through: "posts_tags"
end
end
8.3 预加载 #
elixir
def list_posts_with_comments do
from(p in Post, preload: [:comments])
|> Repo.all()
end
def get_post_with_comments!(id) do
Repo.get!(Post, id) |> Repo.preload(:comments)
end
九、聚合查询 #
9.1 计数 #
elixir
def count_users do
from(u in User, select: count(u.id))
|> Repo.one()
end
def count_active_users do
from(u in User, where: u.active == true, select: count(u.id))
|> Repo.one()
end
9.2 求和与平均 #
elixir
def average_age do
from(u in User, select: avg(u.age))
|> Repo.one()
end
def total_posts do
from(p in Post, select: sum(p.view_count))
|> Repo.one()
end
9.3 分组 #
elixir
def posts_count_by_author do
from(p in Post,
group_by: p.author_id,
select: {p.author_id, count(p.id)}
)
|> Repo.all()
end
十、总结 #
10.1 核心概念 #
| 概念 | 说明 |
|---|---|
| Repository | 数据库连接和操作 |
| Schema | 数据模型定义 |
| Changeset | 数据验证和转换 |
| Query | 类型安全的查询 |
10.2 常用操作 #
| 操作 | 函数 |
|---|---|
| 插入 | Repo.insert/1 |
| 查询 | Repo.all/1, Repo.get/2 |
| 更新 | Repo.update/1 |
| 删除 | Repo.delete/1 |
10.3 下一步 #
现在你已经了解了Ecto基础,接下来让我们学习 Schema与迁移,深入了解数据模型定义!
最后更新:2026-03-28