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_idcard_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