使用ORM创建嵌套关系并最小化查询

时间:2011-05-01 20:51:02

标签: php codeigniter orm codeigniter-datamapper

编辑3

在阅读了船载之后,我真的不认为任何ORM或系统通常都可以在我使用的较少查询中建立我想要的有组织对象的关系。如果任何可以提供一个可能的例子,我会吻你。

编辑2 嵌套for循环我认为是运行的最佳解决方案

Total_queries = 1 + 2(Slides_in_project) + Shapes_in_project
                |                |                        \
  Query to get project           |                         \
                                 |                          \
        Query to get slides and double because of points     \
                                                              \
                                              Get all the shapes in the project

我希望有一个更好的例子,因为要填充简单的项目,我可能会运行200-500个查询。这很糟糕。

编辑2

嗯,我已经玩了一段时间了,我有一些结果,但我不认为它们是“正确的”方式,这对我很重要。我所做的是使用where_related方法来获取正确的对象,但我认为我的查询计数仍然很高,我知道ORM可以做得更好。当我使用相关的地方创建适当的层次结构时,我必须使用嵌套的foreach循环,我不喜欢这样。这意味着无用的查询。

这是我提出的解决方案

function get_project_points($link_id)
{
    echo "<pre>";
    foreach($this->where('link_id', $link_id)->get()->slide->where_related('project', 'id', $this)->get() as $slide){
        echo $slide->id."<br>";
        foreach($slide->shape->where_related('slide', 'id', $slide->id)->get() as $shape){
            echo "\t".$shape->id."<br>";
            foreach ($shape->point->where_related('shape', 'id', $shape->id)->get() as $point) {
                echo "\t\t".$point->id."<br>";
            }
        }
    }
}

这会输出一个漂亮的分层结构,正如您所看到的那样,用对象/数组填充来替换回声很容易。

我宁愿拥有的是一个链式命令,如果可能的话,也会做同样的事情,这样做范围也不是问题。

某些链条更像是

$this->where('link_id', $link_id)->get()
    ->slide->where_related('project', 'id', $this)->get()
    ->shape->where_related('slide', 'id', $slide->id)->get()
    ->point->where_related('shape', 'id', $shape->id)->get()

当然,这并没有达到与嵌套的foreach循环相同的结果,但我想知道的是可以链接关系并填充没有嵌套foreach的对象

所以我只做了一些分析,嵌套的foreach循环在一个小项目上生成了63个查询,并且花了差不多半秒来生成结果。这真的太慢了​​。必须有更好的查询。

__________编辑1

以下所有信息都很棒,但我一直在玩它,我似乎无法找到任何工作关系,更不用说3层了。我已经尝试了几乎所有我能想到的东西并阅读了文档,但是因为某些原因我的大脑不喜欢ORM。

我想在slides中回显所有project的ID。我将列出我尝试过但无济于事的清单。我的模型结构与下面相同,我只是添加了方法。

class Project extends DataMapper {
    var $has_many = array("slide");

    function get_project_slides($link_id)
    {
        $this->where('link_id', $link_id)
            ->where_related('slides', 'project_id' 
                $this->where('link_id', $link_id)->get()->id
            )
        ->get();
    }

}

我已经尝试过我认为幻灯片方法中的逻辑相反的东西。

我做错了什么......你如何构建ORM关系?


原始问题

我第一次使用ORM,我遇到了很大的问题,可视化如何构建代码以从关系中提取数据。

我使用DataMapper作为我的ORM CodeIgniter。我的安装工作正常,我阅读了所有文档,我无法理解如何获取控制器中的信息

+-----------+    +------------+    +---------+    +----------+
|  projects |    | slides     |    | shapes  |    |  points  |
+-----------+    +------------+    +---------+    +----------+
|    id     |    |  id        |    | id      |    | id       |
+-----------+    | project_id |    |slide_id |    | shape_id |
                 +------------+    +---------+    | x        |
                                                  | y        |
                                                  +----------+

模特 -

project.php

class Project extends DataMapper {
    var $has_many = array("slide");
}

//  End of project.php
//  Location: ./application/models/project.php 

slide.php

<?php

class Slide extends DataMapper {
    var $has_many = array("shape");
    var $has_one = array("project");
}

//  End of slide.php
//  Location: ./application/models/slide.php 

shape.php

<?php

class Shape extends DataMapper {
    var $has_many = array("point");
    var $has_one = array("slide");
}

//  End of shape.php
//  Location: ./application/models/shape.php 

point.php

<?php

class Point extends DataMapper {
    var $has_one = array("shape");
}

//  End of point.php
//  Location: ./application/models/point.php 

以上应该在项目之间创建一个降序的 - >多个关系 - &gt; slide-&gt; shapes-&gt; points

您如何开始处理信息?当我没有使用ORM时,我处理模型中的所有数据处理对于ORM模型来说是不正确的?假设您想获得项目1中所有形状的所有点,您将如何构建这样的调用?

如果您希望这样做有帮助,我真的不需要特定的代码。我真正需要的是关于如何可视化如何对对象进行分层的一些想法,以便您可以在任何层处理每个对象。

6 个答案:

答案 0 :(得分:0)

我不确定Datamapper是如何做到的,但我有一个自定义的GenericObject模型,用于Codeigniter,它像这样做ORM:

class Article extends GenericObject
{
    public $relationships = array ( "has_many" => array ("comments"), "has_one" => array ("users") );
}
class Comments extends GenericObject
{
    public $relationships = array ( "belongs_to" => array ("articles", "users"), "has_one" => array ("users") );
}
class Users extends GenericObject
{
    public $relationships = array ( "has_many" => array ("comments", "articles") );
}

如果我想从用户处获得所有内容,那么我可以执行以下操作:

$User = new User( $some_user_id );
$User->boot_relations("all");

foreach ($User->Comments as $Comment)
{
    echo $Comment->title."<br />";
    echo $Comment->body."<br />";
    echo "written by ".$User->username;
}

所以它可以相当优雅(或者至少我喜欢这么认为)。

答案 1 :(得分:0)

对于大多数关系数据,我通常需要延迟加载对象。我不是PHP开发人员,但这是我在伪代码中所做的。

class Projects {
    var slides = null;
    function getSlides() {
        if(slides == null) {
            slides = getSlidesForProject(this.id);
        }
        return slides;
    }
}

class Slides {
    var shapes = null;
    function getShapes() {
        if(shapes == null) {
            shapes = getShapesForSlide(this.id);
        }
        return slides;
    }
}

class Shapes {
    //... same as Projects.getSlides and Slides.getShapes
}

不幸的是,如果您需要获取项目的所有积分,这会导致多次调用数据库。

对于任何MVC解决方案,我建议使用轻型控制器和繁重的模型来简化代码重用和测试。

答案 2 :(得分:0)

首先:我对CI自己的ORM实现一无所知,但是当我看到你在做什么时,要么它缺少某些功能,要么你以错误的方式使用它(来自你的编辑#2)。

然而在Propel中(只是提到这个,因为这是我最常使用的时间,Doctrine是另一个好的选择)这些事情很容易完成,特别是在新的1.6分支中,使用流畅的接口。只需查看docs on Relationships即可。这看起来不像你想要使用的东西吗? :P

答案 3 :(得分:0)

根据我的理解,您希望在给定每个表之间的约束的情况下检索具有所有关联点的一个项目。

检查此sqlFiddle:http://sqlfiddle.com/#!2/9ed46/2

架构:

CREATE TABLE project
    (
     id int auto_increment primary key, 
     name varchar(20)
    );

CREATE TABLE slide
    (
     id int auto_increment primary key, 
     name varchar(20),
     project_id int
    );

CREATE TABLE shape
    (
     id int auto_increment primary key, 
     name varchar(20),
     slide_id int
    );


CREATE TABLE point
    (
     id int auto_increment primary key, 
     name varchar(20),
     shape_id int
    );

请求:

select project.id as project_id, project.name as project_name,
       slide.id as   slide_id,   slide.name as   slide_name,
       shape.id as   shape_id,   shape.name as   shape_name,
       point.id as   point_id,   point.name as   point_name

from project

left join slide on slide.project_id = project.id
left join shape on shape.slide_id   = slide.id
left join point on point.shape_id   = shape.id

where project.id = 1

返回如下内容:

PROJECT_ID PROJECT_NAME SLIDE_ID SLIDE_NAME SHAPE_ID SHAPE_NAME POINT_ID POINT_NAME
1          fst_project  1       fst_slide   1        fst_shape  1        first_pt
1          fst_project  1       fst_slide   1        fst_shape  2        2nd_pt
...

通过处理此输出,您可以构建一个类似于您想要的对象树,1个查询中的所有内容。但是这种后处理可能需要一些时间。你必须遍历每一点。

答案 4 :(得分:0)

查看Granada

自述文件:

class User extends Model {
         public static $_table = 'user';

         public function post(){
               return $this->has_many('Post');
         }
         public function avatar(){
               return $this->has_one('Avatar');
         }
   }

您可以在人际关系中加入关系!

$user_list = Model::factory('User')->with(array('post'=>array('with'=>'comments')),'avatar')->find_many();

它会产生3个查询:

SELECT * FROM user 
SELECT * FROM avatar WHERE user.id IN (......)
SELECT * FROM post WHERE user.id IN (.....)

答案 5 :(得分:-1)

首先,我很抱歉打破这个,但CodeIgniter的DataMapper实际上是ActiveRecord模式的变体。

如果您关心,则将真实DataMapper模式与其对应项ActiveRecord进行比较。简而言之,事实上,在DM模式中,域对象不知道存储的类型(甚至是存在)。它的使用方式与$mapper->store( $user );类似。

  

“赞成对象组成   继承。“GoF

那说..


如果我正确阅读这些例子,它应该像这样工作: (我假设'实体'之间的关系已经建立)

class Project extends DataMapper
{
   // --- the rest of your stuff 

   public function get_all_points()
   {
      $points = array();

      $slides = $this->slide->get()->all;


      foreach ( $slides as $slide )
      {
         $shapes = $slide->shape->get()->all;

         foreach ( $shapes as $shape )
         {
            $points = array_merge( $point = $shape->point->get();
         }
      }

      return $points;

   }


}

然后你可以使用像

这样的东西
$project = new Project;
$all_points = $project->where( 'id' , 1 )->get_all_points();

foreach ( $all_points as $point )
{
   $point->x = 0;
   $point->save();
}

应该收集与ID为1的项目相关的所有点,并将X值设置为0并将每个值存储在数据库中..并不是任何理智的人都会这样做。< / p>



我没有使用任何形式的ORM,这就是为什么我真的希望我弄错了,因为这看起来像是一个令人厌恶的。