我不确定这是不是一个错误,或者我做错了什么,但是我尝试了很多工作来实现这个目标而我做不到。我希望你们能帮忙。
基本上我有一对一的关系,我需要lazyLoad。关系树在我的项目中有点大,我无法在没有承诺的情况下加载它。
我面临的问题是,当我保存一个孩子时,父更新生成的sql缺少更新字段:UPDATE `a` SET WHERE `id` = 1
当我不使用lazyLoading(Promises)时,这非常有效。
我使用生成的代码工具设置了一个简单的示例。
实体A.
@Entity()
export class A {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(
(type: any) => B,
async (o: B) => await o.a
)
@JoinColumn()
public b: Promise<B>;
}
实体B.
@Entity()
export class B {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(
(type: any) => A,
async (o: A) => await o.b)
a: Promise<A>;
}
main.ts
createConnection().then(async connection => {
const aRepo = getRepository(A);
const bRepo = getRepository(B);
console.log("Inserting a new user into the database...");
const a = new A();
a.name = "something";
const aCreated = aRepo.create(a);
await aRepo.save(aCreated);
const as = await aRepo.find();
console.log("Loaded A: ", as);
const b = new B();
b.name = "something";
const bCreated = bRepo.create(b);
bCreated.a = Promise.resolve(as[0]);
await bRepo.save(bCreated);
const as2 = await aRepo.find();
console.log("Loaded A: ", as2);
}).catch(error => console.log(error));
产量
Inserting a new user into the database...
query: SELECT `b`.`id` AS `b_id`, `b`.`name` AS `b_name` FROM `b` `b` INNER JOIN `a` `A` ON `A`.`bId` = `b`.`id` WHERE `A`.`id` IN (?) -- PARAMETERS: [[null]]
query: START TRANSACTION
query: INSERT INTO `a`(`id`, `name`, `bId`) VALUES (DEFAULT, ?, DEFAULT) -- PARAMETERS: ["something"]
query: UPDATE `a` SET WHERE `id` = ? -- PARAMETERS: [1]
query failed: UPDATE `a` SET WHERE `id` = ? -- PARAMETERS: [1]
如果我删除实体的承诺,一切正常:
实体A.
...
@OneToOne(
(type: any) => B,
(o: B) => o.a
)
@JoinColumn()
public b: B;
}
实体B.
...
@OneToOne(
(type: any) => A,
(o: A) => o.b)
a: A;
}
main.ts
createConnection().then(async connection => {
...
const bCreated = bRepo.create(b);
bCreated.a = as[0];
await bRepo.save(bCreated);
...
产量
query: INSERT INTO `b`(`id`, `name`) VALUES (DEFAULT, ?) -- PARAMETERS: ["something"]
query: UPDATE `a` SET `bId` = ? WHERE `id` = ? -- PARAMETERS: [1,1]
query: COMMIT
query: SELECT `A`.`id` AS `A_id`, `A`.`name` AS `A_name`, `A`.`bId` AS `A_bId` FROM `a` `A`
我还创建了一个git项目来说明这一点,并且易于测试。
1)使用承诺(不工作)https://github.com/cuzzea/bug-typeorm/tree/promise-issue
2)没有懒惰装载(工作)https://github.com/cuzzea/bug-typeorm/tree/no-promise-no-issue
我在你的promise-issue
存储库分支中找到了一些东西,发现了一些有趣的东西:
UPDATE
查询由初始await aRepo.save(aCreated);
触发,而不是由B
的插入和随后的外键分配给a.b
。在a.b = null
之前分配aRepo.create(a)
避免了这个问题。a.b = null;
之前添加初始化aRepo.create(a)
可以避免意外的无效UPDATE
;即:
const a = new A();
a.name = "something";
a.b = null;
const aCreated = aRepo.create(a);
await aRepo.save(aCreated);
async
函数来inverseSide
论证@OneToOne()
(即async (o: B) => await o.a)
)是不正确的。
The documentation表示这应该只是(o: B) => o.a
,OneToOne
的仿制药也证实了这一点。
TypeORM将在将值传递给此函数之前解析承诺,并且async
函数返回另一个Promise
而不是正确的属性值。class A
的一个实例传递给aRepo.create()
。这不是必要的;你可以直接将你的实例传递给aRepo.save(a)
。 Repository.create()
只是将提供的对象中的值复制到实体类的新实例中。似乎.create()
在它们尚不存在时创造了承诺。这实际上可能是导致这个问题的原因;在调用aCreated
之前记录aRepo.save(aCreated)
表明承诺未得到解决。
事实上,删除aRepo.create(a)
步骤(并将保存更改为await aRepo.save(a);
似乎也避免了这个问题。也许Repository<T>.create()
处理延迟加载属性时,它的参数已经是instanceof T
?我会调查一下。我也尝试将typeorm
包升级到typeorm@next
(0.3.0-alpha.12),但问题似乎仍然存在。
我刚刚注意到你已经为此记录了一个GitHub issue;我将着眼于创建一个测试用例,以便在接下来的几天内进行演示。
我希望这足以回答你的问题!
在进一步的代码跟踪之后,似乎上面列表中的项目4)是此问题的原因。
在RelationLoader.enableLazyLoad()
中,TypeORM使用自己的getter和setter重载@Entity实例上的惰性属性访问器 - 例如Object.defineProperty(A, 'b', ...)
。重载的属性访问器加载并缓存相关的B
记录,返回Promise<B>
。
Repository.create()
迭代所创建实体的所有关系,并且 - 当提供对象时 - 从提供的值构建新的相关对象。但是,此逻辑不考虑Promise
对象,并尝试直接从Promise的属性构建相关实体。
因此,在你的情况下,aRepo.create(a)
建立一个新的A
,迭代A
关系(即.b
),并从B
上的Promise
建立一个空的a.b
。新的B
没有定义任何属性,因为Promise
实例不共享任何属性B
。然后因为没有指定id
,没有为aRepo.save()
定义外键名称和值,导致您遇到的错误。
因此,简单地将a
直接传递给aRepo.save()
并删除aRepo.create(a)
步骤似乎是这种情况下的正确行动方案。
这是一个应该修复的问题 - 但我不认为这是一个简单的解决方案,因为这真的需要Repository.create()
能够await
承诺;目前无法实现,因为Repository.create()
不是异步的。