如何将 Repo.transaction 与 Ecto 的原始 sql 查询一起使用
How to use Repo.transaction with raw sql queries with Ecto
我正在将 Node.js 应用程序转换为 Elixir,我需要在 Ecto 中使用原始 SQL 查询,因为我有一些相当复杂的 SQL 查询。我在一笔交易中大约有 10 个。
我也是 Elixir 的新手,我正在尝试使用 Repo.transaction 而不是尝试滚动我自己的交易。
所以我有大约 10 个:
raw_query = "INSERT INTO user_servers(user_id, server_id, server_type_code, position, active_flag, create_date, created_by)
SELECT , , 'SERVER', (COALESCE(MAX(position), 0) + 1), 'Y', CURRENT_TIMESTAMP,
FROM user_servers WHERE server_type_code = 'SERVER' and user_id = ;"
Ecto.Adapters.SQL.query(MyRepo, raw_query, [value1, value2, value3, value4])
如果我这样做
Repo.transaction fn ->
Ecto.Adapters.SQL.query(MyRepo, raw_query, [value1, value2, value3, value4])
Ecto.Adapters.SQL.query(MyRepo, raw_query, [value1, value2, value3, value4])
end
其中一个失败了,它会在失败时自动回滚任何查询吗?还有一种方法可以命名这些查询吗?
例如,也许我希望我的第一个查询被命名为 user_servers 这样当事务返回时我可以用 {:ok, :user_servers: results} 检索它就像我可以用Ecto.Multi?
我建议你看看Ecto.Multi
。它将允许您命名每个查询。如果较早的查询失败,它还会停止管道。
defmodule UserServers do
alias Ecto.Multi
alias MyApp.Repo
def insert(value1, value2, value3, value4) do
raw_query =
~s{INSERT INTO user_servers(user_id, server_id, server_type_code, position, active_flag, create_date, created_by)
SELECT , , 'SERVER', (COALESCE(MAX(position), 0) + 1), 'Y', CURRENT_TIMESTAMP,
FROM user_servers WHERE server_type_code = 'SERVER' and user_id = ;}
Multi.new
|> Multi.run(:one, &query(&1, [value1, value2, value3, value4]))
|> Multi.run(:two, &query(&1, [value1, value2, value3, value4]))
|> Repo.transaction
end
def query(raw_query, values) do
Ecto.Adapters.SQL.query(MyRepo, raw_query,values)
end
end
因此,为了添加到 Steve Pallen 的回答中,我将添加更多内容,以防将来有更多人有同样的问题。
所以我构建了一个实用程序 class 来帮助我进行交易:
defmodule ChatApp.DatabaseUtils do
import Ecto
def execute_and_load_into_model_from_multi(repo, sql, params, model) do
fields = Ecto.Adapters.SQL.query!(repo, sql, params)
|> load_into_model(model, repo)
{:ok, fields}
end
defp load_into_model(response, model, repo) do
Enum.map(response.rows, fn row ->
fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
Map.put(map, key, value)
end)
Ecto.Schema.__load__(model, nil, nil, nil, fields,
&Ecto.Type.adapter_load(repo.__adapter__, &1, &2))
end)
end
def execute_and_load_from_multi(repo, sql, params) do
fields = Ecto.Adapters.SQL.query!(repo, sql, params)
|> load_into
{:ok, fields}
end
defp load_into(response) do
Enum.map(response.rows, fn row ->
fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
Map.put(map, String.to_atom(key), value)
end)
end)
end
end
基本上这些都是为了
execute_and_load_into_model_from_multi
之后会将您的查询映射到模式,您只需传递您的存储库、模型和查询即可。我使用的架构是我的第一个 Multi-运行,它是 ChatApp.Roles.RoleMasters,这是我架构的名称。
另一个:
execute_and_load_from_multi
会将您的查询映射到地图而不是模型,它还会将字符串转换为原子,因此您可以使用任一函数获取相同的地图。
Multi 继续进行事务需要 {:ok, result} ok 告诉它不要触发回滚。
这就是查询的样子,我将使用我能想到的所有内容来获得我能想到的最多示例。
multi =
Multi.new
|> Multi.run(:role_masters, fn %{} ->
qry = "SELECT * FROM reph.role_masters " <> "WHERE role_name = ?"
DatabaseUtils.execute_and_load_into_model_from_multi(ChatApp.Repo, qry, ["STARTER"], ChatApp.Roles.RoleMasters)
end)
|> Multi.run(:role_master_parameters, fn %{role_masters: role_masters} ->
IO.inspect role_masters
DatabaseUtils.execute_and_load_from_multi(ChatApp.Repo, "SELECT * FROM reph.role_master_parameters WHERE role_master_id = ? AND parameter = 'ROLE_NAME'", [Map.get(Enum.at(role_masters, 0), :id)])
end)
|> Multi.run(:insert_server_type_parameters, fn %{} ->
ChatApp.Repo.insert(%ChatApp.Servers.ServerTypeParameters{server_type_code: "VOICE_CHANNEL", parameter_namespace: "PERMS.VOICE", parameter: "TEST_VOICE", parameter_value_type: "FLAG3", parameter_default_value: "I", description: "Members with this permission can change their voice", parameter_role_capable_flag: "Y", active_flag: "Y"})
end)
case ChatApp.Repo.transaction(multi) do
{:ok, result} ->
IO.puts "results"
IO.inspect result.role_master_parameters
IO.inspect result.insert_server_type_parameters
{:error, result } ->
#do stuff with an error
end
如您所见,我将这些查询命名为:role_masters,一个是:role_master_parameters,:insert_server_type_parameters.
现在在这个部分
fn %{role_masters: role_masters}
你可以这样命名:
fn %{role_masters: whatevers}
As multi 将匹配之前的 role_masters 原子。
同样在最后你可以看到结果,它本质上是在地图的地图中出现的,所以我的地图将包含 3 个地图,它们由我的 3 个查询命名:
:role_masters, :role_master_parameters, :insert_server_type_parameters
如果你最后需要对它们做任何事情,只需在交易完成后通过这样做来抓住它们
result.role_masters, result.role_master_parameters, result.insert_server_type_parameters
希望这对将来需要原始处理的人有所帮助 SQL。
我正在将 Node.js 应用程序转换为 Elixir,我需要在 Ecto 中使用原始 SQL 查询,因为我有一些相当复杂的 SQL 查询。我在一笔交易中大约有 10 个。
我也是 Elixir 的新手,我正在尝试使用 Repo.transaction 而不是尝试滚动我自己的交易。
所以我有大约 10 个:
raw_query = "INSERT INTO user_servers(user_id, server_id, server_type_code, position, active_flag, create_date, created_by)
SELECT , , 'SERVER', (COALESCE(MAX(position), 0) + 1), 'Y', CURRENT_TIMESTAMP,
FROM user_servers WHERE server_type_code = 'SERVER' and user_id = ;"
Ecto.Adapters.SQL.query(MyRepo, raw_query, [value1, value2, value3, value4])
如果我这样做
Repo.transaction fn ->
Ecto.Adapters.SQL.query(MyRepo, raw_query, [value1, value2, value3, value4])
Ecto.Adapters.SQL.query(MyRepo, raw_query, [value1, value2, value3, value4])
end
其中一个失败了,它会在失败时自动回滚任何查询吗?还有一种方法可以命名这些查询吗?
例如,也许我希望我的第一个查询被命名为 user_servers 这样当事务返回时我可以用 {:ok, :user_servers: results} 检索它就像我可以用Ecto.Multi?
我建议你看看Ecto.Multi
。它将允许您命名每个查询。如果较早的查询失败,它还会停止管道。
defmodule UserServers do
alias Ecto.Multi
alias MyApp.Repo
def insert(value1, value2, value3, value4) do
raw_query =
~s{INSERT INTO user_servers(user_id, server_id, server_type_code, position, active_flag, create_date, created_by)
SELECT , , 'SERVER', (COALESCE(MAX(position), 0) + 1), 'Y', CURRENT_TIMESTAMP,
FROM user_servers WHERE server_type_code = 'SERVER' and user_id = ;}
Multi.new
|> Multi.run(:one, &query(&1, [value1, value2, value3, value4]))
|> Multi.run(:two, &query(&1, [value1, value2, value3, value4]))
|> Repo.transaction
end
def query(raw_query, values) do
Ecto.Adapters.SQL.query(MyRepo, raw_query,values)
end
end
因此,为了添加到 Steve Pallen 的回答中,我将添加更多内容,以防将来有更多人有同样的问题。
所以我构建了一个实用程序 class 来帮助我进行交易:
defmodule ChatApp.DatabaseUtils do
import Ecto
def execute_and_load_into_model_from_multi(repo, sql, params, model) do
fields = Ecto.Adapters.SQL.query!(repo, sql, params)
|> load_into_model(model, repo)
{:ok, fields}
end
defp load_into_model(response, model, repo) do
Enum.map(response.rows, fn row ->
fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
Map.put(map, key, value)
end)
Ecto.Schema.__load__(model, nil, nil, nil, fields,
&Ecto.Type.adapter_load(repo.__adapter__, &1, &2))
end)
end
def execute_and_load_from_multi(repo, sql, params) do
fields = Ecto.Adapters.SQL.query!(repo, sql, params)
|> load_into
{:ok, fields}
end
defp load_into(response) do
Enum.map(response.rows, fn row ->
fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
Map.put(map, String.to_atom(key), value)
end)
end)
end
end
基本上这些都是为了
execute_and_load_into_model_from_multi
之后会将您的查询映射到模式,您只需传递您的存储库、模型和查询即可。我使用的架构是我的第一个 Multi-运行,它是 ChatApp.Roles.RoleMasters,这是我架构的名称。
另一个:
execute_and_load_from_multi
会将您的查询映射到地图而不是模型,它还会将字符串转换为原子,因此您可以使用任一函数获取相同的地图。
Multi 继续进行事务需要 {:ok, result} ok 告诉它不要触发回滚。
这就是查询的样子,我将使用我能想到的所有内容来获得我能想到的最多示例。
multi =
Multi.new
|> Multi.run(:role_masters, fn %{} ->
qry = "SELECT * FROM reph.role_masters " <> "WHERE role_name = ?"
DatabaseUtils.execute_and_load_into_model_from_multi(ChatApp.Repo, qry, ["STARTER"], ChatApp.Roles.RoleMasters)
end)
|> Multi.run(:role_master_parameters, fn %{role_masters: role_masters} ->
IO.inspect role_masters
DatabaseUtils.execute_and_load_from_multi(ChatApp.Repo, "SELECT * FROM reph.role_master_parameters WHERE role_master_id = ? AND parameter = 'ROLE_NAME'", [Map.get(Enum.at(role_masters, 0), :id)])
end)
|> Multi.run(:insert_server_type_parameters, fn %{} ->
ChatApp.Repo.insert(%ChatApp.Servers.ServerTypeParameters{server_type_code: "VOICE_CHANNEL", parameter_namespace: "PERMS.VOICE", parameter: "TEST_VOICE", parameter_value_type: "FLAG3", parameter_default_value: "I", description: "Members with this permission can change their voice", parameter_role_capable_flag: "Y", active_flag: "Y"})
end)
case ChatApp.Repo.transaction(multi) do
{:ok, result} ->
IO.puts "results"
IO.inspect result.role_master_parameters
IO.inspect result.insert_server_type_parameters
{:error, result } ->
#do stuff with an error
end
如您所见,我将这些查询命名为:role_masters,一个是:role_master_parameters,:insert_server_type_parameters.
现在在这个部分
fn %{role_masters: role_masters}
你可以这样命名:
fn %{role_masters: whatevers}
As multi 将匹配之前的 role_masters 原子。
同样在最后你可以看到结果,它本质上是在地图的地图中出现的,所以我的地图将包含 3 个地图,它们由我的 3 个查询命名:
:role_masters, :role_master_parameters, :insert_server_type_parameters
如果你最后需要对它们做任何事情,只需在交易完成后通过这样做来抓住它们
result.role_masters, result.role_master_parameters, result.insert_server_type_parameters
希望这对将来需要原始处理的人有所帮助 SQL。