2017-04-23 25 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 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調用是否正常,但控制器在數據庫更新之前發射了兩次。這可能是客戶端,我不想考慮的東西。所以我只是要添加一個唯一的密鑰。

+0

你可以張貼'CardMeta.changeset'源和代碼,你是從調用'bury'? – Dogbert

+0

嘗試樂觀鎖定,看看是否有幫助。 https://hexdocs.pm/ecto/0.9.0/Ecto.Model.OptimisticLock.html –

+0

您可能會遇到控制器更新操作的併發問題。你可以嘗試的一件事是將埋葬代碼放入GenServer中並觸發它。這樣你就可以確定沒有其他請求進來了。 –

回答

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 
0

我相信你可以通過添加在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 

如果簡化版,解決您的問題,請在此處添加您的數據模型解決這個!