字符串的自定义自动递增字段提供重复条目
Custom auto incrementing fields for string giving duplicate entries
我目前正在开发一个应用程序,要求在系统中注册的每个学生在注册时都有一个唯一的注册号,每次注册都会自动递增。 2019年第一个注册的学生注册号格式为“01/19”,第890个学生注册号为“890/19”等
注册数据是从 kobo data collection 工具上传的,因此它会在非常短的时间间隔内到达我的端点,一次可以完成近 200 次提交。
我发现在 1000 条记录中有将近 27 个重复注册号。这就是我实现注册号生成逻辑的方式
def registration_changeset(student, attrs) do
student |> changeset(attrs) |> Accounts.add_registration_number()
end
然后Accounts context有如下添加注册号的代码
def add_registration_number(changeset) do
struct = changeset.data.__struct__
registration_number =
case struct |> last() |> Repo.one() do
nil -> _create_new_registration_number()
resource -> _increment_registration(resource.registration_number)
end
put_change(changeset, :registration_number, registration_number)
end
我打赌最后创建的学生在上述情况下拥有最新的注册号
有没有更好的实现方式?
最简单的解决办法就是让自增ID作为注册号的第一部分,将两位数的年份单独存储为一个字段。然后在模型中,您只需使用一个 'get_registration_number' 方法来组合这两者。这解决了重复问题。
不利之处在于,您将有 890/19 作为 2019 年的最后一名学生,然后 891/20 作为 2020 年的第一名学生,而不是 01/20。但是您的示例似乎也不能很好地处理这种情况,并且暗示但不清楚这是一项要求。
如果是,您可以不让 ID 成为第一部分,而是创建一个像以前一样自动递增的列(id_num,比如说),然后在年份更改时重置下一个值该列上的 1。如果您想成为偏执狂,您可以说 'id_num' 列和 'two_digit_year' 列一起形成一个唯一键。
TL;DR:处理这个问题的最简单方法是让数据库处理它。
这里需要一些同步代码:)
创建一个专门用于单一目的的进程:生成数字。 GenServer.handle_call/3
已经满足您的所有需求,进程邮箱是完美的队列,OTP 会为您做所有事情。
defmodule MyApp.RegNumHelper do
@moduledoc false
use GenServer
def start_link(opts),
do: GenServer.start_link(__MODULE__, opts, name: name)
def add_registration_number(changeset),
do: GenServer.call(__MODULE__, {:reg_num, changeset})
@impl true
def init(opts), do: {:ok, opts}
@impl true
def handle_call({:reg_num, changeset}, _from, state) do
# your logic assigning changeset
{:reply, changeset, state}
end
end
这种方法还有另一个优点:由于进程已经是有状态的,您实际上不需要每次都查询数据库。只需在进程启动时查询它并将当前数字保存到 state
.
我目前正在开发一个应用程序,要求在系统中注册的每个学生在注册时都有一个唯一的注册号,每次注册都会自动递增。 2019年第一个注册的学生注册号格式为“01/19”,第890个学生注册号为“890/19”等
注册数据是从 kobo data collection 工具上传的,因此它会在非常短的时间间隔内到达我的端点,一次可以完成近 200 次提交。
我发现在 1000 条记录中有将近 27 个重复注册号。这就是我实现注册号生成逻辑的方式
def registration_changeset(student, attrs) do
student |> changeset(attrs) |> Accounts.add_registration_number()
end
然后Accounts context有如下添加注册号的代码
def add_registration_number(changeset) do
struct = changeset.data.__struct__
registration_number =
case struct |> last() |> Repo.one() do
nil -> _create_new_registration_number()
resource -> _increment_registration(resource.registration_number)
end
put_change(changeset, :registration_number, registration_number)
end
我打赌最后创建的学生在上述情况下拥有最新的注册号
有没有更好的实现方式?
最简单的解决办法就是让自增ID作为注册号的第一部分,将两位数的年份单独存储为一个字段。然后在模型中,您只需使用一个 'get_registration_number' 方法来组合这两者。这解决了重复问题。
不利之处在于,您将有 890/19 作为 2019 年的最后一名学生,然后 891/20 作为 2020 年的第一名学生,而不是 01/20。但是您的示例似乎也不能很好地处理这种情况,并且暗示但不清楚这是一项要求。
如果是,您可以不让 ID 成为第一部分,而是创建一个像以前一样自动递增的列(id_num,比如说),然后在年份更改时重置下一个值该列上的 1。如果您想成为偏执狂,您可以说 'id_num' 列和 'two_digit_year' 列一起形成一个唯一键。
TL;DR:处理这个问题的最简单方法是让数据库处理它。
这里需要一些同步代码:)
创建一个专门用于单一目的的进程:生成数字。 GenServer.handle_call/3
已经满足您的所有需求,进程邮箱是完美的队列,OTP 会为您做所有事情。
defmodule MyApp.RegNumHelper do
@moduledoc false
use GenServer
def start_link(opts),
do: GenServer.start_link(__MODULE__, opts, name: name)
def add_registration_number(changeset),
do: GenServer.call(__MODULE__, {:reg_num, changeset})
@impl true
def init(opts), do: {:ok, opts}
@impl true
def handle_call({:reg_num, changeset}, _from, state) do
# your logic assigning changeset
{:reply, changeset, state}
end
end
这种方法还有另一个优点:由于进程已经是有状态的,您实际上不需要每次都查询数据库。只需在进程启动时查询它并将当前数字保存到 state
.