2017-04-23 3 views
0

私のコードは、データベースへのまれな二重または三重の挿入をもたらしており、なぜ私は紛失してしまったのですか?再現することは非常に難しいですが、タイムスタンプを見ると、作成された時刻が基本的に同じ時刻になることがわかります。私はそれがCardMetaがまだ見つからないときにだけ起こると信じています。Ecto insert_or_update複数の挿入を作成していますか?

私は一意のキーを追加するか、トランザクションでラップする必要があります。

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

editから埋め込む呼び出す

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 

Iが、一方、last_seenフィールドは重複行の間の異なるマイクロ秒である気づいcreate_atフィールドにはその解決策がありません。したがって、insert_or_update呼び出しは問題ないと思われますが、コントローラはDBを更新する前に2回起動しています。これは、私が考えたくないクライアント側のものかもしれません。だから私はちょうどユニークなキーを追加するつもりです。

+0

あなたは 'CardMeta.changeset'とあなたから' bury'を呼び出しているコードのソースを投稿することができますか? – Dogbert

+0

楽観的なロックを試してみてください。 https://hexdocs.pm/ecto/0.9.0/Ecto.Model.OptimisticLock.html –

+0

コントローラーの更新操作で並行性の問題が発生する可能性があります。あなたが試すことができる1つのことは、埋葬コードをGenServerに入れて呼び出しをトリガーすることです。こうすることで、他のリクエストが来ていないことを確認することができます。 –

答えて

1

、:

することができます
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 
0

私はあなたがこれはあなたの問題を解決しdoes't場合は、データモデルは、ここで追加してください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 

に複合主キーを追加することによってこの問題を解決できると信じて!あなたはCardMetaの主キーを変更したくない場合は、あなたが移行をデータベースに固有のインデックス制約を置くことができます@ aliCnaの答えに代わるものとして

関連する問題