You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Elixir Ecto中使用Scrivener对嵌入的LineItem分页

解决Scrivener对Ecto Embeds_Many字段的分页问题

我明白你遇到的问题了——当你尝试对Order里的line_items(embeds_many类型)分页时,Scrivener把整个LineItem列表当成了单个条目,导致分页完全不符合预期。这是因为Scrivener默认是对数据库查询返回的顶层记录进行分页,而你的查询返回的是包含LineItem列表的Order记录,自然会把整个列表算成一条。

下面给你两种可行的解决方案,你可以根据业务场景选择:

方案1:使用Scrivener.List对内存中的LineItem列表分页

如果你的LineItem数据量不大,或者暂时没办法修改数据库结构,那么可以先获取完整的LineItem列表,再用scrivener_list库在内存中完成分页。

步骤:

  1. 先添加scrivener_list依赖到mix.exs:
defp deps do
  [
    # 其他已有的依赖
    {:scrivener_list, "~> 2.0"}
  ]
end

执行mix deps.get完成安装。

  1. 修改代码逻辑,先获取目标Order,再对其line_items列表分页:
# 先拿到符合条件的Order记录
order = Repo.get_by(Order, id: ^recipients_id, status: "In Recipients List")

# 对内存中的LineItem列表进行分页
page = Scrivener.List.paginate(order.line_items, conn.params)

这样得到的page就是标准的Scrivener.Page结构,每个条目都是单个LineItem,分页参数(page_size、page_number)也会正常生效。

优缺点:实现简单,无需修改数据库;但如果LineItem数量极大,内存分页会占用较多资源,效率不如数据库层面分页。

方案2:将LineItem改为独立Schema(Has_Many关联)

如果业务允许,把LineItem从embeds_many改成独立的数据库表,用has_many关联到Order,这样就能直接用Scrivener在数据库层面分页,效率更高。

步骤:

  1. 创建LineItem的Schema和迁移文件:
# lib/texting/sales/line_item.ex
defmodule Texting.Sales.LineItem do
  use Ecto.Schema
  import Ecto.Changeset

  schema "line_items" do
    field :name, :string
    field :person_id, :integer
    field :phone_number, :string
    belongs_to :order, Texting.Sales.Order

    timestamps()
  end

  @doc false
  def changeset(line_item, attrs) do
    line_item
    |> cast(attrs, [:name, :person_id, :phone_number])
    |> validate_required([:name, :person_id, :phone_number])
  end
end

迁移文件:

defmodule Texting.Repo.Migrations.CreateLineItems do
  use Ecto.Migration

  def change do
    create table(:line_items) do
      add :name, :string
      add :person_id, :integer
      add :phone_number, :string
      add :order_id, references(:orders, on_delete: :delete_all)

      timestamps()
    end

    create index(:line_items, [:order_id])
  end
end
  1. 修改Order的Schema,替换embeds_many为has_many:
defmodule Texting.Sales.Order do
  use Ecto.Schema
  import Ecto.Changeset

  schema "orders" do
    field :counts, :integer
    field :status, :string
    has_many :line_items, Texting.Sales.LineItem, on_delete: :delete_all
    timestamps()
  end

  # 原有的changeset逻辑...
end
  1. 直接查询LineItem并分页:
query = from li in LineItem,
        join: o in Order,
        on: li.order_id == o.id,
        where: o.id == ^recipients_id and o.status == "In Recipients List"

page = Repo.paginate(query, conn.params)

这样分页是在数据库层面完成的,返回的每个条目都是单个LineItem,分页逻辑完全符合预期,而且适合大数据量场景。

优缺点:数据库层面分页,效率更高,支持大量数据;但需要修改数据库结构和Schema,有一定的迁移成本。

内容的提问来源于stack exchange,提问作者Tae

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:40:36