【问题标题】:Doctrine DTO with native SQL queries带有原生 SQL 查询的 Doctrine DTO
【发布时间】:2016-04-16 21:44:01
【问题描述】:

我目前有一个相当复杂的本机 SQL 查询,用于报告目的。鉴于它处理的数据量,这是处理它的唯一有效方法是使用本机 SQL。

这工作正常,并从标量结果返回一个数组数组。

为了使结果与项目中的所有其他结果集保持一致,我想做的是使用数据传输对象 (DTO)。返回简单 DTO 对象的数组。

这些与 DQL 配合得非常好,但我看不出将它们与本机 SQL 一起使用。这有可能吗?

【问题讨论】:

    标签: doctrine-orm dto


    【解决方案1】:

    Doctrine 可以将原始 SQL 查询的结果映射到实体,如下所示:

    http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/native-sql.html

    除非您也愿意使用 DQL,否则我看不到对 DTO 的支持,因此不存在直接解决方案。我尝试了一个效果很好的简单解决方法,因此这里有 DQL 和非 DQL 方法来实现您的目标。

    这些示例是使用 Laravel 和 Laravel Doctrine 扩展构建的。

    1. DTO

    以下 DTO 支持 DQL 绑定和自定义映射,因此构造函数必须能够使用和不使用参数。

    <?php namespace App\Dto;
    
    /**
     * Date with corresponding statistics for the date.
     */
    class DateTotal
    {
        public $taskLogDate;
        public $totalHours;
    
        /**
         * DateTotal constructor.
         *
         * @param $taskLogDate The date for which to return totals
         * @param $totalHours The total hours worked on the given date
         */
        public function __construct($taskLogDate = null, $totalHours = null)
        {
            $this->taskLogDate = $taskLogDate;
            $this->totalHours = $totalHours;
        }
    
    }
    
    1. 使用 DQL 获取结果

    这是标准版本,使用 DQL。

    public function findRecentDateTotals($taskId)
    {
        $fromDate = new DateTime('6 days ago');
        $fromDate->setTime(0, 0, 0);
        $queryBuilder = $this->getQueryBuilder();
    
        $queryBuilder->select('NEW App\Dto\DateTotal(taskLog.taskLogDate, SUM(taskLog.taskLogHours))')
            ->from('App\Entities\TaskLog', 'taskLog')
            ->where($queryBuilder->expr()->orX(
                $queryBuilder->expr()->eq('taskLog.taskLogTask', ':taskId'),
                $queryBuilder->expr()->eq(0, ':taskId')
            ))
            ->andWhere(
                $queryBuilder->expr()->gt('taskLog.taskLogDate', ':fromDate')
            )
            ->groupBy('taskLog.taskLogDate')
            ->orderBy('taskLog.taskLogDate', 'DESC')
            ->setParameter(':fromDate', $fromDate)
            ->setParameter(':taskId', $taskId);
    
        $result = $queryBuilder->getQuery()->getResult();
        return $result;
    }
    
    1. 支持具有本机 SQL 的 DTO

    这是一个简单的助手,可以将原始 SQL 查询的数组结果编组为对象。它也可以扩展到做其他事情,也许是自定义更新等等。

    <?php namespace App\Dto;
    
    use Doctrine\ORM\EntityManager;
    
    /**
     * Helper class to run raw SQL.
     *
     * @package App\Dto
     */
    class RawSql
    {
        /**
         * Run a raw SQL query.
         *
         * @param string $sql The raw SQL
         * @param array $parameters Array of parameter names mapped to values
         * @param string $className The class to pack the results into
         * @return Object[] Array of objects mapped from the array results
         * @throws \Doctrine\DBAL\DBALException
         */
        public static function query($sql, $parameters, $className)
        {
            /** @var EntityManager $em */
            $em = app('em');
            $statement = $em->getConnection()->prepare($sql);
            $statement->execute($parameters);
            $results = $statement->fetchAll();
    
            $return = array();
            foreach ($results as $result) {
                $resultObject = new $className();
                foreach ($result as $key => $value) {
                    $resultObject->$key = $value;
                }
                $return[] = $resultObject;
            }
            return $return;
        }
    }
    
    1. 运行原始 SQL 版本

    该函数的使用和调用方式与其他存储库方法相同,只是调用了上述帮助程序以自动将数据转换为对象。

    public function findRecentDateTotals2($taskId)
    {
        $fromDate = new DateTime('6 days ago');
    
        $sql = "
            SELECT
              task_log.task_log_date AS taskLogDate,
              SUM(task_log.task_log_hours) AS totalHours
            FROM task_log task_log
            WHERE (task_log.task_log_task = :taskId OR :taskId = 0) AND task_log.task_log_date > :fromDate
            GROUP BY task_log_date
            ORDER BY task_log_date DESC        
        ";
    
        $return = RawSql::query(
            $sql,
            array(
                'taskId' => $taskId,
                'fromDate' => $fromDate->format('Y-m-d')
            ),
            DateTotal::class
        );
        return $return;
    }
    
    1. 注意事项

    我不会太快放弃 DQL,因为它可以执行大多数类型的 SQL。然而,我最近也参与了构建管理报告的工作,在管理信息领域,SQL 查询可以与整个 PHP 文件一样大。在那种情况下,我会加入你并放弃 Doctrine(或任何其他 ORM)。

    【讨论】:

    • 伟大而详细的解决方案我真的很喜欢它。我只是好奇你在拥有复杂 DTO 时如何进行 DTO 转换,或者我应该说嵌套 DTO。例如,具有标量和对象字段或对象列表的父 DTO
    • 我在尝试使 DTO 流行起来时遇到的问题是,你最终会复制 Doctrine 会为你做的很多事情,结果会变得一团糟。如果涉及到非平凡的关系,我会考虑在正常的 Doctrine 查询旁边运行本机查询。例如,您有一个简单的结果表,其中包含一列或两列聚合。使用原始查询获取聚合并通过主结果集的 ID 对其进行键控。然后只需查找每一行的统计信息。好处:能够遍历 Doctrine 管理的关系并一次性获取所有聚合。
    猜你喜欢
    • 2012-02-25
    • 2019-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多