原始问题
我正在Doctrine中进行获取连接,使用具有复合键而没有其他已定义字段的连接表,并将不正确的数据加载到我的实体中.这是Doctrine中的错误,还是我做错了什么?
以下是一个说明问题的简单示例.
创建三个实体:
/**
* @ORM\Entity
* @ORM\Table(name="driver")
*/
class Driver
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=255);
*/
private $name;
/**
* @ORM\OneToMany(targetEntity="DriverRide", mappedBy="driver")
*/
private $driverRides;
function getId() { return $this->id; }
function getName() { return $this->name; }
function getDriverRides() { return $this->driverRides; }
}
/**
* @ORM\Entity
* @ORM\Table(name="driver_ride")
*/
class DriverRide
{
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Driver", inversedBy="driverRides")
* @ORM\JoinColumn(name="driver_id", referencedColumnName="id")
*/
private $driver;
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Car", inversedBy="carRides")
* @ORM\JoinColumn(name="car", referencedColumnName="brand")
*/
private $car;
function getDriver() { return $this->driver; }
function getCar() { return $this->car; }
}
/**
* @ORM\Entity
* @ORM\Table(name="car")
*/
class Car
{
/**
* @ORM\Id
* @ORM\Column(type="string", length=25)
* @ORM\GeneratedValue(strategy="NONE")
*/
private $brand;
/**
* @ORM\Column(type="string", length=255);
*/
private $model;
/**
* @ORM\OneToMany(targetEntity="DriverRide", mappedBy="car")
*/
private $carRides;
function getBrand() { return $this->brand; }
function getModel() { return $this->model; }
function getCarRides() { return $this->carRides; }
}
使用以下数据填充相应的数据库表:
INSERT INTO driver (id, name) VALUES (1, 'John Doe');
INSERT INTO car (brand, model) VALUES ('BMW', '7 Series');
INSERT INTO car (brand, model) VALUES ('Crysler', '300');
INSERT INTO car (brand, model) VALUES ('Mercedes', 'C-Class');
INSERT INTO car (brand, model) VALUES ('Volvo', 'XC90');
INSERT INTO car (brand, model) VALUES ('Dodge', 'Dart');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'Crysler');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'Mercedes');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'Volvo');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'BMW');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'Dodge');
使用此代码来水合Driver实体并显示其内容:
$qb = $em->createQueryBuilder();
$driver = $qb->select('d, dr, c')
->from('Driver', 'd')
->leftJoin('d.driverRides', 'dr')
->leftJoin('dr.car', 'c')
->where('d.id = 1')
->getQuery()->getSingleResult();
print '<p>' . $driver->getName() . ':';
foreach ($driver->getDriverRides() as $ride) {
print '<br>' . $ride->getCar()->getBrand() . ' ' . $ride->getCar()->getModel();
}
预期产量:
John Doe:
宝马7系
Crysler 300
道奇飞镖
梅赛德斯C级
沃尔沃XC90
实际产量:
John Doe:
宝马7系
道奇飞镖
道奇飞镖
沃尔沃XC90
沃尔沃XC90
这里有关联实体的奇怪重复.具体地,子实体#3被复制为#2,#5被复制为#4等,并且子实体#2,#4等根本没有被加载.这种模式是一致的和可重复的.
我的代码有问题吗?为什么Doctrine无法正确地将数据从数据库表映射到实体?
补充说明
如果查询所有游乐设施(也就是驱动程序和汽车之间的关联)并执行获取连接,则会出现同样的问题.这是一个重要的案例,因为@ qaqar-haider的答案对于这种情况不起作用.
假设下表数据
INSERT INTO driver (id, name) VALUES (1, 'John Doe');
INSERT INTO driver (id, name) VALUES (2, 'Erika Mustermann');
INSERT INTO car (brand, model) VALUES ('BMW', '7 Series');
INSERT INTO car (brand, model) VALUES ('Crysler', '300');
INSERT INTO car (brand, model) VALUES ('Mercedes', 'C-Class');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'Crysler');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'Mercedes');
INSERT INTO driver_ride (driver_id, car) VALUES (1, 'BMW');
INSERT INTO driver_ride (driver_id, car) VALUES (2, 'BMW');
INSERT INTO driver_ride (driver_id, car) VALUES (2, 'Crysler');
想象一下使用fetch-joins的以下查询
$qb = $em->createQueryBuilder();
$rides = $qb->select('dr, d, c')
->from('DriverRide', 'dr')
->leftJoin('dr.driver', 'd')
->leftJoin('dr.car', 'c')
->getQuery()->getResult();
foreach ($rides as $ride) {
print $ride->driver->getName() . ': ' . $ride->getCar()->getModel();
}
预期产量:
约翰·多伊:克莱斯勒
John Doe:梅赛德斯
John Doe:宝马
Erika Mustermann:宝马
Erika Mustermann:克莱斯勒
实际产量:
约翰·多伊:克莱斯勒
John Doe:梅赛德斯
John Doe:梅赛德斯
Erika Mustermann:宝马
Erika Mustermann:宝马
再一次,结果的数量是正确的,但是这些关联是混合的.
这是原始问题的简化测试用例,不受任何WHERE或GROUP BY子句的约束.
最佳答案 这是Doctrine中的一个错误.不幸的是,相关的Doctrine源代码非常难看,所以我不确定
my bug fix是最好的,但它确实解决了这个问题.我在这里复制
the explanation from my bug report是为了完整性:
这个bug的主要原因是在ObjectHydrator
课程中.在hydrateRowData()
方法中,$resultPointers用于存储对每个实体类型的最近水合对象的引用.当子实体需要链接到其父实体时,此方法查找$resultPointers以查找对父项的引用,如果找到,则将子项链接到该父项.
问题是$resultPointers是一个实例/对象(而不是本地/方法)变量,每次调用hydrateRowData()时都不会重新初始化,因此它可能保留对前一次方法水合的实体的引用被称为而非当前时间.
在此特定示例中,每次调用hydrateRowData()时,Car实体在DriverRide实体之前被水合.当该方法查找Car实体的父实体时,它第一次找不到任何内容(因为DriverRide尚未完全处理),并且在每次后续调用时,都会找到对前一次水合的DriverRide对象的引用调用了方法(因为尚未为当前行处理DriverRide,并且$resultPointers仍保留对前一行处理结果的引用).
当其他字段添加到DriverRide时,该错误消失,因为这样做恰好导致在HydrorateRowData()中的Car实体之前处理DriverRide实体.发生重复记录是因为这个方法的其他一些奇怪的部分导致子实体被识别为每次调用该方法时都没有连接(并且因此延迟加载)(不计算第一次),所以这些时间(第三,第五,等等.孩子和父母之间的联系恰好是正确的.
我认为基本问题是$resultPointers既不是局部变量也不是每次调用hydrateRowData()时都重新初始化.我想不出任何一种情况,你需要引用一个与上一行数据中的数据一起水合的对象,所以我建议在这个方法的开头简单地重新初始化这个变量.那应该修复bug.