Ecto insert_or_update 创建多个插入?
Ecto insert_or_update creating multiple inserts?
我的代码导致对数据库进行罕见的两次或三次插入,我不知道为什么。很难重现,但我可以查看时间戳以查看创建时间与发生时基本相同。我相信只有当 CardMeta
尚未找到时才会发生。
我认为我需要添加唯一密钥或将其包装在事务中。
def get_or_create_meta(user, card) do
case Repo.all(from c in CardMeta, where: c.user_id == ^user.id,
where: c.card_id == ^card.id) do
[] ->
%CardMeta{}
metas ->
hd metas
end
end
def bury(user, card) do
get_or_create_meta(user, card)
|> Repo.preload([:card, :user])
|> CardMeta.changeset(%{last_seen: DateTime.utc_now(), user_id: user.id, card_id: card.id,
learning: false, known: false, prev_interval: 0})
|> Repo.insert_or_update
end
编辑:添加变更集源
def changeset(struct, params \ %{}) do
struct
|> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
:user_id, :card_id])
|> assoc_constraint(:user)
|> assoc_constraint(:card)
end
从控制器调用 bury
def update(conn, %{"currentCardId" => card_id, "command" => command}) do
# perform some update on card
card = Repo.get!(Card,card_id)
user = Guardian.Plug.current_resource(conn)
case command do
"fail" ->
SpacedRepetition.fail(user, card)
"learn" ->
SpacedRepetition.learn(user, card)
_ ->
SpacedRepetition.bury(user, card)
end
sendNextCard(conn, user)
end
编辑:
我注意到 last_seen 字段在重复的行之间有微秒的差异,而 create_at 字段没有那个分辨率。因此我怀疑 insert_or_update 调用没问题,但控制器在数据库更新之前触发了两次。这可能是客户端的事情,我不想考虑。所以我只是要添加一个唯一的密钥。
我相信你可以通过在 user_id
和 card_id
上添加复合主键来解决这个问题
defmodule Anything.CardMeta do
use Anything.Web, :model
@primary_key false
schema "card_meta" do
field :user_id, :integer, primary_key: true
field :card_id, :integer, primary_key: true
. . .
timestamps()
end
end
如果这不能解决您的问题,请在此处添加您的数据模型!
作为@aliCna 答案的替代方案,如果您不想更改 CardMeta
上的主键,您可以通过迁移在数据库中放置一个唯一索引约束:
defmodule YourApp.Repo.Migrations.AddCardMetaUniqueIndex do
use Ecto.Migration
def change do
create unique_index(
:card_meta,
[:card_id, :user_id],
name: :card_meta_unique_index)
end
end
如果发生冲突,您可以在变更集中处理以产生好的错误:
def changeset(struct, params \ %{}) do
struct
|> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
:user_id, :card_id])
|> assoc_constraint(:user)
|> assoc_constraint(:card)
|> unique_constraint(:user_id, name: :card_meta_unique_index)
end
我的代码导致对数据库进行罕见的两次或三次插入,我不知道为什么。很难重现,但我可以查看时间戳以查看创建时间与发生时基本相同。我相信只有当 CardMeta
尚未找到时才会发生。
我认为我需要添加唯一密钥或将其包装在事务中。
def get_or_create_meta(user, card) do
case Repo.all(from c in CardMeta, where: c.user_id == ^user.id,
where: c.card_id == ^card.id) do
[] ->
%CardMeta{}
metas ->
hd metas
end
end
def bury(user, card) do
get_or_create_meta(user, card)
|> Repo.preload([:card, :user])
|> CardMeta.changeset(%{last_seen: DateTime.utc_now(), user_id: user.id, card_id: card.id,
learning: false, known: false, prev_interval: 0})
|> Repo.insert_or_update
end
编辑:添加变更集源
def changeset(struct, params \ %{}) do
struct
|> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
:user_id, :card_id])
|> assoc_constraint(:user)
|> assoc_constraint(:card)
end
从控制器调用 bury
def update(conn, %{"currentCardId" => card_id, "command" => command}) do
# perform some update on card
card = Repo.get!(Card,card_id)
user = Guardian.Plug.current_resource(conn)
case command do
"fail" ->
SpacedRepetition.fail(user, card)
"learn" ->
SpacedRepetition.learn(user, card)
_ ->
SpacedRepetition.bury(user, card)
end
sendNextCard(conn, user)
end
编辑:
我注意到 last_seen 字段在重复的行之间有微秒的差异,而 create_at 字段没有那个分辨率。因此我怀疑 insert_or_update 调用没问题,但控制器在数据库更新之前触发了两次。这可能是客户端的事情,我不想考虑。所以我只是要添加一个唯一的密钥。
我相信你可以通过在 user_id
和 card_id
defmodule Anything.CardMeta do
use Anything.Web, :model
@primary_key false
schema "card_meta" do
field :user_id, :integer, primary_key: true
field :card_id, :integer, primary_key: true
. . .
timestamps()
end
end
如果这不能解决您的问题,请在此处添加您的数据模型!
作为@aliCna 答案的替代方案,如果您不想更改 CardMeta
上的主键,您可以通过迁移在数据库中放置一个唯一索引约束:
defmodule YourApp.Repo.Migrations.AddCardMetaUniqueIndex do
use Ecto.Migration
def change do
create unique_index(
:card_meta,
[:card_id, :user_id],
name: :card_meta_unique_index)
end
end
如果发生冲突,您可以在变更集中处理以产生好的错误:
def changeset(struct, params \ %{}) do
struct
|> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
:user_id, :card_id])
|> assoc_constraint(:user)
|> assoc_constraint(:card)
|> unique_constraint(:user_id, name: :card_meta_unique_index)
end