laravel中的belongsToMany关系跨多个数据库

时间:2014-08-05 15:42:13

标签: php mysql laravel laravel-4

我的模型A和模型B位于两个不同的数据库中。

现在我在与模型a_bs相同的数据库中有一个名为A的pivot_table。

我已经在模型A

中设置了belongsToMany relatinoship
public function bs()
{
    return $this->belongsToMany('B', 'a_bs', 'a_id', 'b_id');
}

当我尝试像这样访问这种关系时:

$a = A::find($id);
print_r($a->bs->lists('id'));

我收到一个错误,我的数据透视表在模型B的数据库中不存在。这显然是正确的,因为数据透视表位于模型A的数据库中。我怎么能让Laravel知道呢?

不建议将数据透视表放在模型B的数据库中

6 个答案:

答案 0 :(得分:8)

非常简单:

public function bs()
{
    $database = $this->getConnection()->getDatabaseName();
    return $this->belongsToMany('B', "$database.a_bs", 'a_id', 'b_id');
}

我正在动态获取数据库名称,因为我的连接是基于环境变量配置的。 Laravel似乎假设数据透视表与目标关系存在于同一个数据库中,因此这将强制它查找与此方法所在的模型相对应的数据库,即“A”域。

如果您不担心SQLite数据库,即在单元测试的范围内,那就是您所需要的。但如果你是,继续阅读。

首先,前面的例子本身并不充分。 $ database的值最终将成为文件路径,因此您需要将其别名为不会破坏SQL语句的内容,并使其可以被当前连接访问。 "ATTACH DATABASE '$database' AS $name"就是这样做的:

public function bs()
{
    $database = $this->getConnection()->getDatabaseName();
    if (is_file($database)) {
        $connection = app('B')->getConnection()->getName();
        $name = $this->getConnection()->getName();
        \Illuminate\Support\Facades\DB::connection($connection)->statement("ATTACH DATABASE '$database' AS $name");
        $database = $name;
    }
    return $this->belongsToMany('B', "$database.a_bs", 'a_id', 'b_id');
}

警告:事务搞砸了:如果当前连接正在使用事务,则ATTACH DATABASE语句将失败。 可以在执行该语句之后使用上的事务。如果相关连接使用事务,则结果数据将以静默方式呈现为当前数据不可见。这让我疯狂的时间超过了我想要承认的时间,因为我的查询没有错误地运行,但一直空着。似乎只有真正写入附加数据库的数据实际上才能被附加到的数据库访问。

因此,在被强制写入附加数据库之后,您可能仍希望自己的测试得到清理。一个简单的解决方案就是使用$this->artisan('migrate:rollback', ['--database' => $attachedConnectionName]);。但是如果你有多个需要相同表的测试,这不是很有效,因为它迫使他们每次都必须重建它们。

更好的选择是截断表格,但保留其结构:

//Get all tables within the attached database
collect(DB::connection($database)->select("SELECT name FROM sqlite_master WHERE type = 'table'"))->each(function ($table) use ($name) {
        //Clear all entries for the table
        DB::connection($database)->delete("DELETE FROM '$table->name'");
        //Reset any auto-incremented index value
        DB::connection($database)->delete("DELETE FROM sqlite_sequence WHERE name = '$table->name'");
    });
}

这会擦除来自该连接的所有数据,但是没有理由你不能对你认为合适的那种应用某种类型的过滤器。或者,您可以利用SQLite DB是易于访问的文件,并将附加的文件复制到临时文件,并在测试完成后使用它覆盖源。结果在功能上与交易完全相同。

答案 1 :(得分:3)

如果数据库/模式与@NiRR在同一主机服务器上,则只需执行此操作以覆盖第二个连接的默认模式:

return $this->belongsToMany('B', 'real-schema-name.a_bs');

或者

return $this->belongsToMany('A', 'real-schema-name.a_bs');

取决于使用未使用默认架构的连接定义的模型(A或B)。

请记住,执行跨两个服务器的连接查询不可能;它将被执行哪个服务器?每个都缺少预先形成请求所需的一些必要数据。

答案 2 :(得分:2)

您可以在模型类中设置表的数据库:

protected $table = 'A.a_s';

创建数据透视表时必须使用单数形式。

/app/model/A.php

class A extends Eloquent {

  // Set table name (plural) with database name
  protected $table = 'A.a_s';

  // Many to many relation
  public function b_s() {
    return $this->belongsToMany('B');
  } 
}

/app/model/B.php

class B extends Eloquent {

  // Set table name (plural) with database name
  protected $table = 'B.b_s';

}

查询

print_r(A::with('b_s')->where('id', 1)->get()->toArray());

的MySQL

CREATE TABLE `A`.`a_s` (
   `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
   PRIMARY KEY (`id`)
);
CREATE TABLE `B`.`b_s` (
   `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
   PRIMARY KEY (`id`)
);
CREATE TABLE `A`.`a_b` (
  `a_id` int(10) unsigned NOT NULL,
  `b_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`a_id`,`b_id`)
) ENGINE=InnoDB;

INSERT INTO A.a_s VALUES (NULL);
INSERT INTO A.a_s VALUES (NULL);
INSERT INTO B.b_s VALUES (NULL);
INSERT INTO A.a_b VALUES (1,1);
INSERT INTO A.a_b VALUES (1,2);

答案 3 :(得分:2)

仅当两个数据库位于同一连接(服务器)上时才可以执行此操作。

不可能在两个不同的连接(服务器)上执行此操作,因为您将需要在将执行join命令的单个服务器上的所有三个表。

答案 4 :(得分:-1)

我是以下列方式做到的

在env

上定义常量
vcruntime140.dll

我的database.php如下所示

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=system
DB_USERNAME=root
DB_PASSWORD=1234

我设置了下面的租户数据库

'connections' => [


        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', 'localhost'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix' => '',
            'strict' => false,
            'engine' => null,
        ],

        'tenant' => [
                    'driver'   => 'mysql',
                    'host'     => 'localhost',
                    'database' => '',
                    'username' => 'root',
                    'password' => '1234',
                    'prefix'   => '',
                    'charset' => 'utf8',
                    'collation' => 'utf8_unicode_ci',
                    'prefix' => '',
                    'strict' => false,
                    'engine' => null,
                    ],



    ],


    'migrations' => 'migrations',

    ],
  ];

我有一张桌子' security_group'在租户数据库和一个表'用户'在master数据库中,我在master数据库上有一个表security_group_user,用户和安全组之间有很多对很多关系。

我的用户模型如下所示

public static function SetTenantDatabase(){

        //get company name from session

        $company=Company::findOrFail(Session::get('selected_company_id'));
        Config::set('database.connections.tenant.host', 'localhost');
        Config::set('database.connections.tenant.username', 'root');
        Config::set('database.connections.tenant.password', '1234');

        Config::set('database.connections.tenant.database',$company->database);
        DB::reconnect('tenant');
    }

My SecurityGroup模型如下所示

class User extends Authenticatable
{

    public function SecurityGroup() {
        return $this->belongsToMany('App\SecurityGroup', env('DB_DATABASE').'.security_group_user', 'user_id', 'security_group_id');
    }

}

我在用户控制器中执行了以下操作

class SecurityGroup extends Model
{
     protected $connection = 'tenant'; 
}

答案 5 :(得分:-1)

明确设置或定义两个模型的连接效果很好。

protected $connection = 'connection1'; //inside model A
protected $connection = 'connection2'; //inside model B

修改

当Laravel从数据库中获取模型时,如果已在模型中定义了数据库/连接(作为属性),Laravel将在构造SQL时使用该连接的数据库名称。因此,在处理多个连接时,最好为每个模型定义连接。