Laravel 5.3 Eloquent 交易和外键限制

Posted

技术标签:

【中文标题】Laravel 5.3 Eloquent 交易和外键限制【英文标题】:Laravel 5.3 Eloquent transactions and foreign key restrictions 【发布时间】:2017-05-28 22:05:04 【问题描述】:

我正在做一个更大的项目,我们在一个 Postgres 数据库中有多个模式。我们已经在模式之间创建了外键。这是一个例子> 我们有公司架构和用户架构。公司架构有 company_users 表,该表对 user.users 表有外键限制

CREATE TABLE company.company_user
(
  id serial NOT NULL,
  company_id integer NOT NULL,
  user_id integer NOT NULL,
  created_at timestamp(0) without time zone,
  updated_at timestamp(0) without time zone,
  deleted_at timestamp(0) without time zone,
  CONSTRAINT company_user_pkey PRIMARY KEY (id),
  CONSTRAINT company_user_company_id_foreign FOREIGN KEY (company_id)
      REFERENCES company.companies (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT company_user_user_id_foreign FOREIGN KEY (user_id)
      REFERENCES "user".users (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

以下查询在 Postgres 中运行没有问题

BEGIN;
insert into "db"."user"."users" (id,"surname", "firstname", "email", "position", "language_id", "is_super_admin", "updated_at", "created_at") values (156,'Mueller', 'Julianne', 'julianne.mueller1@example.org', 'Nuclear Power Reactor Operator', 41, false, '2017-01-13 12:35:10', '2017-01-13 12:35:10') returning "id";
insert into "db"."company"."company_user" ("company_id", "user_id", "updated_at", "created_at") values (4445, 156, '2017-01-13 12:35:10', '2017-01-13 12:35:10') returning "id";
COMMIT;

但是,如果我通过 Laravel 中的 Eloquent 执行相同的查询

\DB::beginTransaction();
$user = new User(["surname" => 'Mueller',
                  "firstname" => 'Julianne',
                  "email" => 'julianne.mueller1@example.org',
                  "position" => 'Nuclear Power Reactor Operator',
                  "language_id" => 41,
                  "is_super_admin" => false]
);
if (!$user->save()) 
    \DB::rollBack();
    return false;

\Log::error($user->id);
$company_user = new CompanyUser([
    "company_id" => 4445,
    "user_id" => $user->id
]);
if (!$company_user->save()) 
    \DB::rollBack();
    return false;

\DB::commit();

正在抛出以下错误(似乎在表中找不到用户的 id)

PDOException: SQLSTATE[23503]: Foreign key violation: 7 ERROR: insert or update on table "company_user" violates foreign key constraint "company_user_user_id_foreign"

谁能说出为什么这不起作用? \Log::error($user->id) 正在打印插入用户的 id。我尝试使用 DB 侦听器从 Laravel 打印查询,所有查询都以正确的顺序执行,但仍然出现此错误。

【问题讨论】:

【参考方案1】:

好的,我们找到了解决方案。看来我们需要分别为每个模式启动事务 + 每个引用不同模式的外键都应该被创建为延迟。

【讨论】:

哇!这是一个真正的宝石!为了增加您的答案,并为可能遇到相同问题的其他人澄清,当 Lukas 说 each foreign key that are referencing different schema than their own should be created as deferred 时,他指的是引用表上定义的外键。在大多数 IDE(例如 Navicat)中,此选项位于 Foreign Keys 选项卡上,解决此问题的选项标记为 Deferrable: DEFERRABLEDeferred: INITIALLY DEFERRED,而不是 INITIALLY IMMEDIATE。知道这是否可以通过数据库迁移中的内置 Laravel 方法进行设置? 跟进我之前的评论,Laravel 似乎没有内置支持迁移上下文中的可延迟约束。但是,可以在迁移中使用原始语句添加它们,例如:DB::statement('ALTER TABLE foo.bar ADD CONSTRAINT foo_bar_user_id_foreign FOREIGN KEY(user_id) REFERENCES laravel.users(id) DEFERRABLE INITIALLY DEFERRED;'); 另请注意,不能更新现有约束以包含 DEFERRABLE;必须先删除旧约束,然后添加新约束,并且删除和添加必须在单独的迁移中发生。【参考方案2】:

确保CompanyUser 具有$fillable

$fillable = ['user_id', 'company_id'];

另外,请确保users 表中已存在具有此 ID 的用户。也许你需要摆脱事务。

【讨论】:

嘿阿列克谢。它比这复杂一点。是的,companyUser 将它们都设置为可填充,在保存用户后调用 \Log::error($user->id) 的地方有一小段代码,并且在日志中我正在获取 id,所以看起来 Eloquent 正在保存那一排。即使我尝试侦听数据库查询,所有这些查询都以正确的顺序运行,例如首先插入用户表然后插入公司用户,但仍然是相同的错误 您是否尝试在没有事务的情况下这样做? 没有事务它正在工作,但是我需要这些类型的查询的事务。这只是一个例子。但是我将调用不同的 API,当其中一个失败时,我需要将所有更改恢复。 好吧,如果用户还没有创建,那么正常的查询会失败。删除约束如何,但保留密钥以便关系仍然有效?

以上是关于Laravel 5.3 Eloquent 交易和外键限制的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 5.3 Eloquent 关系 - 用户、角色、页面和权限

如何处理带有斜线的 Laravel Eloquent “WHERE”查询? (Laravel 5.3)

Laravel 关系和外键约束

Laravel eloquent 如何动态创建关系?

laravel eloquent 中的一对多关系

Laravel - 使用 Eloquent 从连接关系模型中选择特定列