【问题标题】:composite unique constraint error while updating the changeset更新变更集时出现复合唯一约束错误
【发布时间】:2019-06-11 20:05:29
【问题描述】:

我有一个模式 two_fa_details 其中 answer 和 question_id 是字段,并且两者都是唯一的。 现在,当我尝试先将数据插入其中时,它会被插入,但下次更新它不起作用.. 它说约束错误。

我有一个函数 set_two_factor_details 用于更新表.. 该函数可以很好地在第一次插入数据时工作......但是当我更新它时......它不起作用......我有一个用于这个函数的 PUT API。 这是我的架构 two_fa_details 的迁移文件

def change do
    create table(:two_fa_details) do
      add :answer, :string
      add :userprofile_id, references(:user_profile, on_delete: :nothing)
      add :question_id, references(:questions, on_delete: :nothing)

      timestamps()
    end

    create index(:two_fa_details, [:userprofile_id])
    create index(:two_fa_details, [:question_id])

    create unique_index(:two_fa_details, [:userprofile_id, :question_id], name: :user_twofa_detail)
  end

这是一段sn-p代码

def set_twofactor_details(client_id, twofa_records) do
    user = Repo.get_by(UserProfile, client_id: client_id)
    twofa_records = Enum.map(twofa_records, &get_twofa_record_map/1)

    Enum.map(twofa_records, fn twofa_record ->
      Ecto.build_assoc(user, :two_fa_details)
      |> TwoFaDetails.changeset(twofa_record)
    end)
    |> Enum.zip(0..Enum.count(twofa_records))
    |> Enum.reduce(Ecto.Multi.new(), fn {record, id}, acc ->
      Ecto.Multi.insert_or_update(acc, String.to_atom("twfa_record_#{id}"), record)
    end)|>IO.inspect()
    |> Ecto.Multi.update(
      :update_user,
      Ecto.Changeset.change(user, two_factor_authentication: true, force_reset_twofa: false)
    )
    |> Repo.transaction()|>IO.inspect()
    |> case do
      {:ok, _} ->
        {:ok, :updated}

      {:error, _, changeset, _} ->
        error_string = get_first_changeset_error(changeset)
        Logger.error("Error while updating TWOFA: #{error_string}")
        {:error, 41001, error_string}
    end
  end

输出应该基本上是更新表并返回两个 fa details updated 消息。 但在日志中显示约束错误。请帮我解决这个问题。我是 elixir 的新手。

{:error, :twfa_record_0,
 #Ecto.Changeset<
   action: :insert,
   changes: %{answer: "a", question_id: 1, userprofile_id: 1},
   errors: [
     unique_user_twofa_record: {"has already been taken",
      [constraint: :unique, constraint_name: "user_twofa_detail"]}
   ],
   data: #Accreditor.TwoFaDetailsApi.TwoFaDetails<>,
   valid?: false
 >, %{}}
[error] Error while updating TWOFA: `unique_user_twofa_record` has already been taken

【问题讨论】:

    标签: postgresql elixir phoenix-framework ecto unique-constraint


    【解决方案1】:

    你写道:

    输出应该基本上是更新表并返回两条 fa details updated 消息。

    但代码返回:

     #Ecto.Changeset<
       action: :insert,
       changes: %{answer: "a", question_id: 1, userprofile_id: 1},
       errors: [
         unique_user_twofa_record: {"has already been taken",
          [constraint: :unique, constraint_name: "user_twofa_detail"]}
       ],
       data: #Accreditor.TwoFaDetailsApi.TwoFaDetails<>,
       valid?: false
     >
    

    看看它怎么说action: :insert。所以你不是在更新,而是在插入,这解释了错误。

    insert_or_update 只会在记录是从数据库中加载的情况下更新记录。在您的代码中,您是从头开始构建记录,因此它们将始终是插入。在将它们传递给变更集之前,您需要使用 Repo.get 或类似的方法来获取它们,以便您最终可以调用 insert_or_update

    【讨论】:

    • 我可以在insert_or_update 函数中使用:on_conflict 选项吗??
    • 感谢您的帮助...但是使用 upserts forecto 解决了它
    • 是的! upsert 是解决这个问题的另一个可能的选择,好点!
    【解决方案2】:

    我尝试使用upserts 来代替ecto 它奏效了。 这是一个sn-p的代码供参考

    Ecto.Multi.insert_or_update(acc, String.to_atom("twfa_record_#{id}"), record,
           on_conflict: :replace_all_except_primary_key,
           conflict_target: [:userprofile_id, :question_id] )
    

    【讨论】:

    • 如果它解决了您的问题,请接受这个答案。
    猜你喜欢
    • 1970-01-01
    • 2017-01-10
    • 2012-10-10
    • 1970-01-01
    • 1970-01-01
    • 2016-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多