Ecto 条件更新插入 - Ecto.StaleEntryError

问题描述 投票:0回答:2

我正在尝试完成智能更新插入,我将其定义为:

  • 如果数据库中没有具有相同 id 的模型,请执行 INSERT
  • 如果数据库中存在具有相同 id 的条目并且该条目较新(updated_at 字段),请不要更新
  • 如果数据库中存在具有相同 id 的条目并且该条目较旧(updated_at 字段),请执行更新

我看到 repo.insert 可以选择将查询作为

:on_conflict
选项传递。 我决定编写满足我需求的简约源代码。

update_query =
  from s in User,
     where: s.id == ^id,
     where: s.updated_at < ^updated_at,
     update: ^[set: Map.to_list(data)]

%User{}
|> Ecto.Changeset.change(data)
|> @repo.insert(conflict_target: :id, on_conflict: update_query)

此代码仅在发生插入时才有效。发生冲突时会导致

(Ecto.StaleEntryError) attempted to insert a stale struct:
错误。我可以从 update_query 中删除
where: s.id == ^id, where: s.updated_at < ^updated_at
并删除此错误,但是随后我会丢失所需的 Updated_at 检查。 所需的 postgres 代码如下所示

INSERT INTO mytable (id, entry) VALUES (42, '2021-05-29 12:00:00')
ON CONFLICT (id)
   DO UPDATE SET entry = EXCLUDED.entry, ......
      WHERE mytable.entry < EXCLUDED.entry;
elixir ecto
2个回答
0
投票

我认为做以下事情可能会更容易:

def smart_upsert(%{id: id, updated_at: updated_at} = data) do
    query =
      from u in User,
      where: u.id == ^id,
      where: u.updated_at < ^updated_at
     
    User
    |> Repo.get_by(query)
    |> case do
      nil ->
        %User{}

      existing_user ->
        existing_user
    end
    |> User.changeset(data)
    |> Repo.insert_or_update()
  end

这无疑需要 2 次数据库操作。


0
投票

刚才遇到了这个“问题”,经过一番调查,发现发生

Ecto.StaleEntryError
是因为条件意味着没有要更新的行。 https://subvisual.com/blog/posts/145-upsert-statements-with-ecto/ 上的文章主要帮助我解决了这个问题。

我必须从更新条件中删除

where: s.id == ^id
子句 - 因为这已经是在插入上下文中运行时给定的 - 并且还向
insert
添加以下子句:

stale_error_field: :updated_at,
stale_error_message: "is out of date"

所以我做了类似的事情:

cs = changeset(%Data{}, data)
Repo.insert(cs,
  conflict_target: :id,
  on_conflict:
    from(d in Data,
      where: d.updated_at < ^updated_at,
      update: [set: ^Map.to_list(cs.changes)]
    ),
  stale_error_field: :updated_at,
  stale_error_message: "is out of date"
)

这允许我运行插入,当尝试插入太旧的内容时,它会返回错误变更集:

%Ecto.Changeset{errors: [updated_at: {"is out of date", [stale: true]}]}

然后您可以在周围的代码中轻松处理这一点。例如,您可能想记录有关接收过时更新的信息。

(我必须做的另一件事是确保

updated_at
永远不会是
nil
,否则
from
条件将不起作用,抱怨
comparison with nil is forbidden as it is unsafe
。)

© www.soinside.com 2019 - 2024. All rights reserved.