2017-03-10 55 views
1

我有一個實體Node,它引用自身以創建樹狀結構。在Ecto中加載樹狀結構

這裏是遷移:

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

而且這裏的模式定義:

schema "nodes" do 
    field :name, :string 
    belongs_to :parent, Node 
    has_many :children, Node, foreign_key: :parent_id 
end 

我想用這種方式來加載整個樹:

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) 

其中:

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 

,但我得到:

** (FunctionClauseError) no function clause matching in Ecto.drop_meta/1 

一般情況下,我想我與外生ORM應如何使用的理解鬥爭。大多數教程僅顯示如何提取隔離行或使用一個預加載級別的示例。我應該如何加載一個樹狀結構?感謝您的任何幫助。

+0

阿薩就我所看到的,build_assoc使用不當。爲什麼不簡單地將該值設置爲:children,查詢結果包含所有值,包括parent_id。 當談到你實際面臨的問題時,Michal的答案如下 - 使用遞歸查詢如果你使用posgresql。如果您經常閱讀並且很少在此表中添加新行,請使用嵌套集。 –

回答

4

Ecto不是常規的ORM,因爲大多數ORM試圖完全抽象數據庫,因此它更接近底層的數據庫語義。

這意味着真正的問題不是「如何使用Ecto加載樹狀結構」。但是「如何使用SQL加載樹狀結構」(假設這是你使用的)。

不出所料,得到的答覆是,這是非常複雜的 - 無論是要求每個嵌套級(效率極其低下),recursive queries或改變表示(存儲的完整路徑,而不只是PARENT_ID,或與Nested Set model)一個查詢。

如果只保留parent_id最簡單的方法,可能是在正確的位置加載所有內容(優點是隻執行一個查詢)之前加載所有內容並進行適當的縫合。

雖然這並沒有直接回答這個問題(因爲有沒有一個很好的答案),我希望這不會給你的想法去哪裏尋找解決方案,以及如何可以改變的方式,使其更容易。