【问题标题】:Ecto: How to preload a belongs_to association of a has_many association?Ecto:如何预加载 has_many 关联的 belongs_to 关联?
【发布时间】:2017-04-19 09:17:27
【问题描述】:

我有一个 Meetup 模型 has_many :rsvpsRsvp 模型 belongs_to :user

这是我的终点:

def index(conn, _params) do
  one_hour_ago = Timex.now
                 |> Timex.shift(hours: -1)
                 |> Timex.to_unix
  meetups = from(m in Meetup, where: m.timestamp >= ^one_hour_ago)
            |> Repo.all
            |> Repo.preload(:rsvps)
  render conn, meetups: meetups
end

我的看法

def render("index.json", %{ meetups: meetups }) do
  render_many(meetups, ParrotApi.MeetupView, "meetup.json")
end

def render("meetup.json", %{ meetup: meetup }) do
  %{
    id: meetup.id,
    rsvped_users: Enum.map(meetup.rsvps, fn rsvp ->
      rsvp |> Repo.preload(:user)
      user = rsvp.user
      %{
        image_url: user.image_url,
        interests: user.interests,
      }
    end),
  }
end

但是,** (Plug.Conn.WrapperError) ** (KeyError) key :image_url not found in: #Ecto.Association.NotLoaded<association :user is not loaded> 失败

Repo 导入视图感觉不对。有没有办法在控制器中做到这一点?

【问题讨论】:

  • 在控制器中做|> Repo.preload(rsvps: [:user])怎么样?
  • 成功了!!!哇!谢谢!!
  • 我建议不要在视图中预加载 - 这会导致很多痛苦和性能问题。视图应该是纯函数 - 没有副作用 - 这使得它们非常容易测试,可缓存等。您的实现也受到 N+1 查询的影响 - 对于每个 rsvp,您将为用户单独查询。从控制器执行此操作只会发出一个查询。

标签: elixir phoenix-framework ecto


【解决方案1】:

Ecto 为此目的支持嵌套预加载。你只需要改变:

|> Repo.preload(:rsvps)

到:

|> Repo.preload(rsvps: [:user])

在控制器中,所有 Meetup 的所有 RSVP 都将有效地预加载其 user 字段。


您几乎不应该在循环中调用 preload (for / Enum.each / Enum.map 等),因为它违背了预加载的主要目的 -- avoiding N+1 queries

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-09
    • 1970-01-01
    相关资源
    最近更新 更多