【问题标题】:Laravel Many to many - Unexpected result set on ->select()Laravel 多对多-->select() 上的意外结果集
【发布时间】:2015-05-13 13:45:19
【问题描述】:

我想知道是否有人可以提供帮助,因为我已经碰壁并且仍在学习 Laravel ORM。谁能解释为什么,当我跑步时:

public function locationTags(){
    return $this->hasMany('App\UserHasLocationTags', 'user_id')
        ->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id');
}

我得到这个结果集:(snipped...)

{
    "id": 1,
    "created_at": "2015-05-13 13:04:56",
    "updated_at": "2015-05-13 13:04:56",
    "email": "REMOVED",
    "firstname": "REMOVED",
    "lastname": "REMOVED",
    "location_id": 0,
    "deleted_at": null,
    "permissions": [],
    "location_tags": [
        {
            "user_id": 1,
            "location_tag_id": 1,
            "id": 1,
            "created_at": "2015-05-13 13:06:28",
            "updated_at": "2015-05-13 13:06:28",
            "name": "Test Tag 0",
            "location_id": 1,
            "deleted_at": null
        },
        {
            "user_id": 1,
            "location_tag_id": 2,
            "id": 2,
            "created_at": "2015-05-13 11:40:21",
            "updated_at": "2015-05-13 12:56:13",
            "name": "Test Tag 123",
            "location_id": 1,
            "deleted_at": null
        }
    ]
}

这是王牌!但是,当我开始从 location_tags 连接中选择我想要的列时,使用:

public function locationTags(){
    return $this->hasMany('App\UserHasLocationTags', 'user_id')
        ->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id')
        ->select('lt.id', 'lt.name');
}

我最终得到:

{
    "id": 1,
    "created_at": "2015-05-13 13:04:56",
    "updated_at": "2015-05-13 13:04:56",
    "email": "REMOVED",
    "firstname": "REMOVED",
    "lastname": "REMOVED",
    "location_id": 0,
    "deleted_at": null,
    "permissions": [],
    "location_tags": []
}

有人可以解释发生了什么吗?并可能指出我正确的方向来限制选择?谢谢!

更新 我也试过:

        $query = \App\User::with(['permissions', 'locationTags' => function($query){
            $query->select('lt.id', 'lt.name');
        }]);

返回相同的结果:(

【问题讨论】:

    标签: php laravel eloquent laravel-5


    【解决方案1】:

    想通了。这里的关键是您必须包含至少一个 Laravel 可以用来映射结果集的键的 select() 值。在我的例子中是user_id,就像这样:

    public function locationTags(){
        return $this->hasMany('App\UserHasLocationTags', 'user_id')
            ->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id')
            ->select('user_id', 'lt.name', 'location_tag_id');
    }
    

    然后返回更好的结果集:

    {
        "id": 1,
        "created_at": "2015-05-13 13:04:56",
        "updated_at": "2015-05-13 13:04:56",
        "email": "REMOVED",
        "firstname": "REMOVED",
        "lastname": "REMOVED",
        "location_id": 0,
        "deleted_at": null,
        "permissions": [],
        "location_tags": [
            {
                "user_id": 1,
                "name": "Test Tag 0",
                "location_tag_id": 1
            },
            {
                "user_id": 1,
                "name": "Test Tag 123",
                "location_tag_id": 2
            }
        ]
    }
    

    希望这对将来的某人有所帮助,因为它让我猜了好几个小时。

    【讨论】:

    • 因为这个,我头疼不已:谢谢!!
    【解决方案2】:

    很抱歉,您在这里有点偏离了方向。关系定义应该只定义关系。这是一个强大的功能,支持 ORM 的许多其他方面。您在这里所做的或多或少是构建自定义查询,严重限制了关系的有效性。

    直到最近,正确的方法看起来像这样。

    // class User
    public function userHasLocationTags() {
        $this->hasMany('App\UserHasLocationTags', 'user_id');
    }
    
    // class UserHasLocationTags
    public function locationTags() {
        $this->hasMany('App\LocationTags', 'location_tag_id');
    }
    

    你会急切地加载所有这样的结果。

    $user = User::where('id', 1)->with('userHasLocationTags.locationTags')->first();
    

    上面的代码产生了 3 个查询。一个获取用户,一个获取所有 UserHasLocationTags,一个获取所有 LocationTags。这在第一次脸红时可能看起来很浪费,但请考虑以下内容。

    $users = User::take(100)->with('userHasLocationTags.locationTags')->get();
    

    同样,这只是 3 个查询,但现在您已经加载了 100 个用户及其所有位置标签。

    但我可以看出你是一个注重效率的人,加载所有中间关系和整个嵌套层次结构可能不适合你。好消息! Laravel 5 为这种情况添加了另一种关系类型。 hasManyThrough(向下滚动一点找到它)。

    经历过很多次

    “多通”关系为通过中间关系访问远距离关系提供了便捷的捷径。例如,一个 Country 模型可能有许多 Post through 一个 User 模型。

    所以在你的情况下,它可能看起来像这样......

    // class User
    public function locationTags()
    {
        return $this->hasManyThrough('App\LocationTags', 'App\UserHasLocationTags');
    }
    
    $users = User::take(100)->with('locationTags')->get();
    

    所以现在我们只剩下两个查询了。如果您觉得需要进一步优化并且只选择特定的列,您可以修改预加载。

    $users = User::take(100)->with(['locationTags' => function ($query)
    {
        $query->select('user_id', 'name', 'location_tag_id');
    })->get();
    

    如果您经常这样做,您应该将其包含在scope 中。

    【讨论】:

    • 已更新。那要好得多。明天试一试。干杯!
    猜你喜欢
    • 2013-06-11
    • 1970-01-01
    • 1970-01-01
    • 2011-08-07
    • 2021-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-09
    相关资源
    最近更新 更多