如何在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库在内存中完成分页。
步骤:
- 先添加
scrivener_list依赖到mix.exs:
defp deps do [ # 其他已有的依赖 {:scrivener_list, "~> 2.0"} ] end
执行mix deps.get完成安装。
- 修改代码逻辑,先获取目标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在数据库层面分页,效率更高。
步骤:
- 创建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
- 修改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
- 直接查询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
相关产品推荐
相关产品推荐

