ElixirEcto模式Skill elixir-ecto-patterns

这个技能是关于在Elixir编程语言中使用Ecto进行数据库操作的全面指南。它涵盖模式定义、变化集验证、查询构建、关联管理、事务处理等,用于构建高性能、可维护的数据库驱动Elixir应用。关键词:Elixir, Ecto, 数据库操作, 模式设计, 查询优化, 数据验证, 后端开发, Phoenix框架。

后端开发 0 次安装 0 次浏览 更新于 3/25/2026

名称: elixir-ecto-patterns 用户可调用: false 描述: 在需要使用Elixir Ecto模式时使用,包括模式、变化集、查询和事务。在构建数据库驱动的Elixir应用程序时使用。 允许工具:

  • Bash
  • Read

Elixir Ecto 模式

掌握Ecto,Elixir的数据库包装器和查询生成器。这个技能涵盖模式、变化集、查询、关联和事务,用于构建健壮的数据库应用程序。

模式定义

defmodule MyApp.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :email, :string
    field :age, :integer
    field :is_active, :boolean, default: true
    field :role, Ecto.Enum, values: [:user, :admin, :moderator]

    has_many :posts, MyApp.Post
    belongs_to :organization, MyApp.Organization

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :age, :is_active, :role])
    |> validate_required([:name, :email])
    |> validate_format(:email, ~r/@/)
    |> validate_number(:age, greater_than: 0, less_than: 150)
    |> unique_constraint(:email)
  end
end

变化集验证

defmodule MyApp.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :title, :string
    field :body, :text
    field :published, :boolean, default: false
    field :tags, {:array, :string}, default: []

    belongs_to :user, MyApp.User

    timestamps()
  end

  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title, :body, :published, :tags, :user_id])
    |> validate_required([:title, :body, :user_id])
    |> validate_length(:title, min: 3, max: 100)
    |> validate_length(:body, min: 10)
    |> foreign_key_constraint(:user_id)
  end

  def publish_changeset(post) do
    post
    |> change(published: true)
  end
end

基础查询

import Ecto.Query

# 获取所有用户
Repo.all(User)

# 通过ID获取用户
Repo.get(User, 1)
Repo.get!(User, 1)  # 如果未找到则抛出异常

# 通过特定字段获取
Repo.get_by(User, email: "user@example.com")

# 使用where子句过滤
query = from u in User, where: u.age > 18
Repo.all(query)

# 选择特定字段
query = from u in User, select: {u.id, u.name}
Repo.all(query)

# 排序结果
query = from u in User, order_by: [desc: u.inserted_at]
Repo.all(query)

# 限制和偏移
query = from u in User, limit: 10, offset: 20
Repo.all(query)

复杂查询

# 结合多个条件
query =
  from u in User,
    where: u.is_active == true,
    where: u.age >= 18,
    order_by: [desc: u.inserted_at],
    limit: 10

Repo.all(query)

# 使用管道语法
User
|> where([u], u.is_active == true)
|> where([u], u.age >= 18)
|> order_by([u], desc: u.inserted_at)
|> limit(10)
|> Repo.all()

# 动态查询
def filter_users(params) do
  User
  |> filter_by_name(params["name"])
  |> filter_by_age(params["min_age"])
  |> Repo.all()
end

defp filter_by_name(query, nil), do: query
defp filter_by_name(query, name) do
  where(query, [u], ilike(u.name, ^"%#{name}%"))
end

defp filter_by_age(query, nil), do: query
defp filter_by_age(query, min_age) do
  where(query, [u], u.age >= ^min_age)
end

关联和预加载

# 预加载关联
user = Repo.get(User, 1) |> Repo.preload(:posts)

# 预加载嵌套关联
user = Repo.get(User, 1) |> Repo.preload([posts: :comments])

# 查询时预加载
query = from u in User, preload: [:posts, :organization]
Repo.all(query)

# 自定义预加载查询
posts_query = from p in Post, where: p.published == true

query = from u in User, preload: [posts: ^posts_query]
Repo.all(query)

# 连接和预加载
query =
  from u in User,
    join: p in assoc(u, :posts),
    where: p.published == true,
    preload: [posts: p]

Repo.all(query)

聚合和分组

# 计数记录
Repo.aggregate(User, :count)

# 带条件计数
query = from u in User, where: u.is_active == true
Repo.aggregate(query, :count)

# 其他聚合
Repo.aggregate(User, :avg, :age)
Repo.aggregate(User, :sum, :age)
Repo.aggregate(User, :max, :age)

# 分组
query =
  from u in User,
    group_by: u.role,
    select: {u.role, count(u.id)}

Repo.all(query)

# 分组带having子句
query =
  from u in User,
    group_by: u.role,
    having: count(u.id) > 5,
    select: {u.role, count(u.id)}

Repo.all(query)

插入和更新

# 使用变化集插入
attrs = %{name: "John", email: "john@example.com", age: 30}

%User{}
|> User.changeset(attrs)
|> Repo.insert()

# 不使用变化集插入
Repo.insert(%User{name: "Jane", email: "jane@example.com"})

# 更新
user = Repo.get(User, 1)

user
|> User.changeset(%{age: 31})
|> Repo.update()

# 更新所有
query = from u in User, where: u.is_active == false
Repo.update_all(query, set: [is_active: true])

# 删除
user = Repo.get(User, 1)
Repo.delete(user)

# 删除所有
query = from u in User, where: u.is_active == false
Repo.delete_all(query)

事务

# 基本事务
Repo.transaction(fn ->
  user = Repo.insert!(%User{name: "Alice"})
  Repo.insert!(%Post{title: "First post", user_id: user.id})
end)

# Multi用于复杂事务
alias Ecto.Multi

Multi.new()
|> Multi.insert(:user, User.changeset(%User{}, user_attrs))
|> Multi.insert(:post, fn %{user: user} ->
  Post.changeset(%Post{}, Map.put(post_attrs, :user_id, user.id))
end)
|> Multi.run(:send_email, fn _repo, %{user: user} ->
  send_welcome_email(user)
end)
|> Repo.transaction()

嵌入模式

defmodule MyApp.Address do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field :street, :string
    field :city, :string
    field :state, :string
    field :zip, :string
  end

  def changeset(address, attrs) do
    address
    |> cast(attrs, [:street, :city, :state, :zip])
    |> validate_required([:city, :state])
  end
end

defmodule MyApp.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    embeds_one :address, MyApp.Address

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name])
    |> cast_embed(:address, required: true)
  end
end

自定义Ecto类型

defmodule MyApp.Encrypted do
  use Ecto.Type

  def type, do: :binary

  def cast(value) when is_binary(value), do: {:ok, value}
  def cast(_), do: :error

  def dump(value) when is_binary(value) do
    {:ok, encrypt(value)}
  end

  def load(value) when is_binary(value) do
    {:ok, decrypt(value)}
  end

  defp encrypt(value) do
    # 加密逻辑
    value
  end

  defp decrypt(value) do
    # 解密逻辑
    value
  end
end

# 在模式中使用
schema "users" do
  field :secret, MyApp.Encrypted
end

何时使用此技能

使用elixir-ecto-patterns当您需要:

  • 构建数据库支持的Elixir应用程序
  • 定义带有验证的模式和数据模型
  • 使用Ecto的DSL编写复杂数据库查询
  • 管理数据库关系和关联
  • 使用变化集处理数据转换
  • 实现事务以确保数据一致性
  • 处理PostgreSQL、MySQL或其他数据库
  • 构建带有数据库访问的Phoenix应用程序
  • 创建健壮的数据验证层

最佳实践

  • 始终使用变化集进行数据验证
  • 预加载关联以避免N+1查询
  • 使用事务处理多步数据库操作
  • 利用Ecto.Multi处理复杂事务逻辑
  • 保持模式聚焦,避免上帝对象
  • 使用虚拟字段处理计算或临时数据
  • 索引外键和频繁查询的字段
  • 在需要时使用片段处理复杂SQL
  • 编写可组合的查询函数
  • 测试数据库约束和验证

常见陷阱

  • 未预加载关联(N+1查询问题)
  • 忘记验证必需字段
  • 未使用事务处理相关操作
  • 硬编码查询而非组合它们
  • 忽略模式中的数据库约束
  • 未正确处理变化集错误
  • 过度使用嵌入模式处理关系数据
  • 外键缺失索引
  • 未使用Repo.transaction处理多步操作
  • 在业务逻辑中暴露原始Ecto查询

资源