使用模型类型根据其表获取另一个表

Posted

技术标签:

【中文标题】使用模型类型根据其表获取另一个表【英文标题】:Use a model type to fetch another table based on its table 【发布时间】:2021-08-17 14:51:24 【问题描述】:

我有一个Todo,而这个Todo 可以有一个SubTodo。每个SubTodo 可以是以下类型:

文字 下拉菜单 布尔值

这些SubTodo 中的每一个都有与其类型相关的细节,因此我决定将其分离到自己的表格中。数据库表结构如下:

todo
- id

sub_todo
- id
- todo_id
- type [dropdown, text, boolean]

sub_todo_dropdown
- id
- sub_todo_id

sub_todo_text
- id
- sub_todo_id

sub_todo_boolean
- id
- sub_todo_id

SubTodo 需要有一个名为 meta 的关系,它将解析特定于哪种类型的字段。我定义了以下关系:

模型/SubTodo.php

// The SubTodo will have meta information on other tables
// that will be depending on the type of the SubTodo.
// To fetch those meta information's we will
// have this set where will map the
// relationship to its model
const META_MODELS = [
    self::SUB_TODO_TYPE_TEXT => SubTodoText::class,
    self::SUB_TODO_TYPE_BOOLEAN => SubTodoBoolean::class,
    self::SUB_TODO_TYPE_DROPDOWN => SubTodoDropdown::class,
];

public function meta(): HasOne

    return $this->hasOne(self::META_MODELS[$this->type], 'sub_todo_id');

但是当我尝试加载这种关系时

$subTodos = SubTodo::with('meta')->paginate();

我收到"message": "Undefined index: "。做完之后

public function meta(): HasOne

    dd($this->type);
    return $this->hasOne(self::META_MODELS[$this->type], 'sub_todo_id');

我收到null。我最好的猜测是模型还没有加载,所以我需要先加载模型,然后调用meta

$subTodos = SubTodo::limit(10)->get()->each(function (SubTodo $subtodo) 
   $subtodo->load('meta');
);

但是这种方法会导致N+1 问题。有什么方法可以实现加载meta 而无需先加载所有模型?这是one to one polymorphic relationship 的好用法吗?

【问题讨论】:

您需要维护三个不同的表还是可以在一个表中完成?如果您的答案是一张表,您可以使用查询范围。 为了更好地维护未来,这需要三个不同的表。例如,dropdown 类型附加了 dropdown,而 text 类型没有附加 dropdown 你是如何设置 $this->type 的?通过任何构造函数或任何 setter 或默认属性值? $this->type 是模型的属性。它直接来自迁移 恐怕您可能在 self::META_MODELS[$this->type] 处遗漏了 $this->type。可能有一种情况,它只适用于 META_MODELS 数组中的三个值中的任何一个,而其他两个值为 null 【参考方案1】:

好吧,我认为除了覆盖模型之外别无他法。

1 创建SubTodoBuilder 类:

<?php


namespace App\Builder;


use Illuminate\Database\Eloquent\Builder;

class SubTodoBuilder extends Builder

  const RELATIONS = [
     "text" => "SubTodoText",
     "boolean" => "SubTodoBoolean",
     "dropdown" => "SubTodoDropdown"
  ];

 /**
  * Eager load the relationships for the models.
  *
  * @param array $models
  * @return array
  */
 public function eagerLoadRelations(array $models): array
 
     foreach ($this->eagerLoad as $name => $constraints) 
         if ($name === "meta") 
             $groupedModels = collect($models)->groupBy("type");
             $models = [];
             foreach ($groupedModels as $type => $subModels) 
                 $relation = self::RELATIONS[$type];
                 $result = $this->eagerLoadRelation($subModels->all(), $relation, $constraints);
                 $models = array_merge($models, $result);
             
         
         //This part may need some modification
         if (strpos($name, '.') === false && $name !== "meta") 
             $models = $this->eagerLoadRelation($models, $name, $constraints);
         
     
     return $models;
 

2 在SubTodo 模型中:

<?php

 namespace App\Models;

 use App\Builder\SubTodoBuilder;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\HasOne;

 class SubTodo extends Model
 
   use HasFactory;


   protected $fillable = [
       "todo_id",
       "type"
   ];

   public function SubTodoText(): HasOne
   
       return $this->hasOne(SubTodoText::class);
   

   public function SubTodoDropDown(): HasOne
   
       return $this->hasOne(SubTodoDropdown::class);
   

   public function SubTodoBoolean(): HasOne
   
       return $this->hasOne(SubTodoBoolean::class);
   

   public function meta(): HasOne
   
       return $this->hasOne("meta");
   

   /**
    * Create a new Eloquent query builder for the model.
    *
    * @param  \Illuminate\Database\Query\Builder  $query
    * @return Builder|static
    */
   public function newEloquentBuilder($query)
   
       return new SubTodoBuilder($query);
   

3 在你的控制器中

public function index()

   $subTodos=SubTodo::with("meta")->get();

现在,如果您有 30 个 SubTodo 需要获取,并且每个 10 个 SubTodo 具有三种类型之一,它将执行 1+3 个查询。

【讨论】:

以上是关于使用模型类型根据其表获取另一个表的主要内容,如果未能解决你的问题,请参考以下文章

c# 强类型数据集,其表适配器未返回预期结果

如何获取已存储在另一个模型中的名称 ID,并使用名称 ID 在 laravel 的数据透视表数据库中存储值?

如何在 mongodb 中使用 $lookup 从该表中获取与另一个表连接的数据

MySQL - 根据另一个表的列计数从一个表中获取记录

如何使用 EFCore 获取一个表的主键计数到另一个表

sql中的条件总和,根据字段中的条件从另一个表中获取正确的总和