在 Ecto 中加载树状结构

Ada*_*uša 2 elixir ecto phoenix-framework

我有一个Node引用自身以创建树状结构的实体。

这是迁移:

create table(:nodes) do
  add :name, :string, null: false, size: 64
  add :parent_id, references(:nodes, on_delete: :nothing)
end
Run Code Online (Sandbox Code Playgroud)

这里是模式定义:

schema "nodes" do
  field :name, :string
  belongs_to :parent, Node
  has_many :children, Node, foreign_key: :parent_id
end
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用这种方法加载整个树:

root_nodes = Repo.all(
  from n in Node,
    where:  is_nil(n.parent_id) # Root nodes don't have a parent
)

nodes = Enum.map(root_nodes, fn(n) ->
  Ecto.build_assoc(n, :children, load_children(n.id))
end)
Run Code Online (Sandbox Code Playgroud)

在哪里:

defp load_children(parent_id) do
  nodes = Repo.all(
    from n in Node,
      where: n.parent_id == ^parent_id
  )
  if nodes != [] do
    # If children aren't empty, apply recursively
    nodes = Enum.map(nodes, fn(n) ->
      Ecto.build_assoc(n, :children, load_children(n.id))
    end)
  end

  nodes
end
Run Code Online (Sandbox Code Playgroud)

但我得到:

** (FunctionClauseError) no function clause matching in Ecto.drop_meta/1
Run Code Online (Sandbox Code Playgroud)

一般来说,我认为我很难理解应该如何使用 Ecto ORM。大多数教程仅展示了如何获取隔离行或使用一级预加载的示例。我应该如何加载树状结构?感谢您的任何帮助。

mic*_*ala 7

Ecto 不是常规 ORM,因为大多数 ORM 试图完全抽象数据库,而 ecto 往往接近底层数据库语义。

这意味着真正的问题不是“如何使用 Ecto 加载树状结构”。但是“我如何使用 SQL 加载树状结构”(假设这是您使用的)。

不出所料,答案非常复杂——要么需要对每个嵌套级别进行一次查询(效率极低)、递归查询或更改表示(存储完整路径而不仅仅是 parent_id,或使用嵌套集模型))。

如果你保持 parent_id-only 方法,最简单的方法可能是预先加载所有内容,并在加载到正确位置后进行适当拼接(优点是您只执行一个查询)。

虽然这不能直接回答问题(因为没有很好的答案),但我希望这确实可以让您知道在哪里寻找解决方案以及如何更改方法以使其更容易。