【问题标题】:Can Doctrine define MySQL's Generated columns?Doctrine 可以定义 MySQL 的 Generated 列吗?
【发布时间】:2016-09-12 11:42:18
【问题描述】:

在 Symfony 实体中使用 mySQL's generated column 是否正确(如果正确,如何使用)?

例如,下例中的GENERATED ALWAYS

CREATE TABLE contacts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    fullname varchar(101) GENERATED ALWAYS AS (concat(first_name,' ',last_name)),
    email VARCHAR(100) NOT NULL
);

我知道这可以在实体 __construct() 中完成,但由 Doctrine/mySQL 处理会更正确吗?比如:

/**
 * @ORM\Column(type="text")
 * @ORM\Generated(concat(first_name,' ',last_name))
 */
private $fullname;

【问题讨论】:

标签: php mysql doctrine-orm symfony


【解决方案1】:

将生成的列映射到 Doctrine 实体是可能的,但它有一些限制:

  • 您不能持久化一个新实体,因为 Doctrine 会尝试插入到生成的列中并导致错误。
  • 架构工具 (orm:schema-tool:update --dump-sql) 将始终认为您需要更改列。

一种可能的解决方法是像过去那样使用触发器:

CREATE TABLE contacts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    fullname varchar(101) NOT NULL,
    email VARCHAR(100) NOT NULL
);

CREATE TRIGGER contacts_before_insert BEFORE INSERT ON contacts
FOR EACH ROW BEGIN
    SET NEW.fullname = (CONCAT(NEW.first_name, ' ', NEW.last_name));
END;

CREATE TRIGGER contacts_before_update BEFORE UPDATE ON contacts
FOR EACH ROW BEGIN
    SET NEW.fullname = (CONCAT(NEW.first_name, ' ', NEW.last_name));
END;

其他可能的解决方法是将更新逻辑移动到实体中,但它不会将更改直接反映到数据库中。

【讨论】:

    【解决方案2】:

    首先,我很抱歉这个答案这么晚了,但我能够通过 DQL 为基本的 SELECT 和 JOIN 查询创建一个解决方法。由于修改了结果实体,我没有使用 UPDATE 查询对此进行测试。

    正如上述用户 dbu 所说,您需要首先遵循本指南: https://www.liip.ch/en/blog/doctrine-and-generated-columns

    这可以防止在运行模式构建器时教义尝试修改生成的列。当实体更新发生时,这没有教义忽略列。

    您需要确保创建一个添加生成列的迁移。我是这样实现的:

    /**
     * Adds a generated column for fullname to the contacts table
     *
     * @param Schema $schema
     */
    public function up(Schema $schema)
    {
        $this->addSql('
            ALTER TABLE
                contacts
            ADD COLUMN 
                fullname varchar(101) GENERATED ALWAYS AS (concat(first_name,' ',last_name));
        ');
    }
    

    有了上述基础,您应该能够让原则使用架构生成器正常生成您的数据库架构,并且在通过迁移添加时不会干扰您生成的列。

    现在下一个问题是确保您的数据可以在您的联系人实体中进行补充,而不会在 UPDATE 和 INSERT 查询期间尝试修改数据库上的结果。

    诀窍是创建另一个实体来扩展您当前的联系人实体,并且仅用于 SELECT 和 JOIN 查询。

    <?php
    
        namespace Embark\ApiBundle\Entity;
    
        use Doctrine\ORM\Mapping as ORM;
    
        /**
         * Generated values from the contacts table
         *
         * @ORM\Table(name="contacts")
         * @ORM\MappedSuperclass
         */
        class ContactGenerated extends Contact
        {
            /**
             *
             * @ORM\Column(name="fullname", type="string", nullable=true)
             */
            private $fullname;
        }
    

    @MappedSuperClass 注释可防止学说架构构建器尝试创建具有相同名称的表。

    然后您可以使用 DQL 获取数据:

        $queryBuilder->select('contact')
            ->from(ContactGenerated::class, 'contact');
    

    这将返回一个 ContactGenerated 实体数组。如果你试图坚持它们,你会遇到问题,你应该把它们当作只读的。

    我将由您来决定如何将这些转换为标准联系人类,这些类会删除不存在的“全名”属性,这将允许您从选择中执行 UPDATE 查询。

    我真的希望这对您有所帮助,就像对我的用例一样 - 有任何问题,请随时提问 :)

    【讨论】:

    • 添加以节省其他一些故障排除。调用$em-&gt;find(ContactGenerated::class, 1)$em-&gt;getRepository(ContactGenerated::class)-&gt;matching(/* ... */.)-&gt;first()-&gt;getFullname() 将尝试在WHERE 子句中查询未知的表别名,导致SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.id' in 'where clause'.
    【解决方案3】:

    当您还需要编写实体时,有一种解决方法可以在 SQL 中添加 Generated Column,但使用 Schema Listener 将其完全隐藏在 Doctrine ORM 中。然后,您只能在 Doctrine DBAL 查询中使用该列,而不能与 QueryBuilder/DQL 一起使用。我在这篇博文中解释了它是如何工作的:https://www.liip.ch/en/blog/doctrine-and-generated-columns

    【讨论】:

      【解决方案4】:

      我相信只是

      /**
       * @ORM\Column(name="fullname", type="text")
       */
      private $fullname;
      

      将像从普通列中一样读取数据。但是你不应该允许编辑这个字段(不要设置)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-08
        • 2012-02-02
        • 1970-01-01
        • 2022-06-15
        • 1970-01-01
        • 2014-10-19
        相关资源
        最近更新 更多