ecto insert_or_update创建多个插入?

时间:2017-04-23 16:35:33

标签: postgresql elixir phoenix-framework ecto

我的代码导致数据库中出现罕见的双重或三重插入,我不知道为什么。重现起来非常困难,但我可以查看时间戳,看看当时创建的时间基本相同。我相信只有在找不到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

从控制器召唤埋葬

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调用是正常的,但控制器在数据库更新之前触发两次。这可能是客户方面的事情,我不想考虑。所以我只想添加一个独特的密钥。

2 个答案:

答案 0 :(得分:1)

作为@ 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

答案 1 :(得分:0)

我相信您可以通过在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

如果这不能解决您的问题,请在此处添加您的数据模型!