cuz*_*zea 5 lazy-loading one-to-one typescript typeorm
我不确定这是不是一个错误,或者我做错了什么,但是我尝试了很多工作来实现这个目标而我做不到.我希望你们能帮忙.
基本上我有一对一的关系,我需要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>;
}
Run Code Online (Sandbox Code Playgroud)
实体B.
@Entity()
export class B {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(
(type: any) => A,
async (o: A) => await o.b)
a: Promise<A>;
}
Run Code Online (Sandbox Code Playgroud)
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));
Run Code Online (Sandbox Code Playgroud)
产量
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]
Run Code Online (Sandbox Code Playgroud)
如果我删除实体的承诺,一切正常:
实体A.
...
@OneToOne(
(type: any) => B,
(o: B) => o.a
)
@JoinColumn()
public b: B;
}
Run Code Online (Sandbox Code Playgroud)
实体B.
...
@OneToOne(
(type: any) => A,
(o: A) => o.b)
a: A;
}
Run Code Online (Sandbox Code Playgroud)
main.ts
createConnection().then(async connection => {
...
const bCreated = bRepo.create(b);
bCreated.a = as[0];
await bRepo.save(bCreated);
...
Run Code Online (Sandbox Code Playgroud)
产量
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`
Run Code Online (Sandbox Code Playgroud)
我还创建了一个git项目来说明这一点,并且易于测试.
1)使用promises(不工作)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;IE:
const a = new A();
a.name = "something";
a.b = null;
const aCreated = aRepo.create(a);
await aRepo.save(aCreated);Run Code Online (Sandbox Code Playgroud)我相当有信心使用async函数inverseSide作为@OneToOne()(ie. async (o: B) => await o.a)) 的参数是不正确的。
文档表明这应该是 just (o: B) => o.a,并且泛型OneToOne也证实了这一点。
TypeORM 将在将值传递给此函数之前解析承诺,并且async函数返回另一个Promise而不是正确的属性值。
我还刚刚注意到您正在传递一个class Ato实例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 问题;我将在接下来的几天内创建一个测试用例来进行演示。
我希望这足以回答您的问题!
经过进一步的代码跟踪,上面列表中的第 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从Promiseon构建一个空的a.b。newB没有定义任何属性,因为Promise实例不共享任何属性B。然后因为没有id指定,所以没有为 定义外键名称和值aRepo.save(),导致您遇到的错误。
因此,在这种情况下,简单地a直接传递aRepo.save()并删除该aRepo.create(a)步骤似乎是正确的操作过程。
然而,这是一个应该解决的问题 - 但我认为这不是一个容易解决的问题,因为这确实需要能够Repository.create()实现awaitPromise;目前无法实现,因为Repository.create()不是异步的。
| 归档时间: |
|
| 查看次数: |
1466 次 |
| 最近记录: |