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>;
}
实体 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
;IE:const a = new A(); a.name = "something"; a.b = null; const aCreated = aRepo.create(a); await aRepo.save(aCreated);
我相当有信心将
async
函数用于(ie. ) 的inverseSide
参数是不正确的。文档表明这应该是 just ,并且泛型也证实了这一点。TypeORM 将在将值传递给此函数之前解析承诺,并且函数返回另一个而不是正确的属性值。@OneToOne()
async (o: B) => await o.a)
(o: B) => o.a
OneToOne
async
Promise
我也刚刚注意到您正在传递一个
class A
to的实例aRepo.create()
。这不是必需的;您可以将您的实例直接传递给aRepo.save(a)
. 只需将Repository.create()
提供的对象中的值复制到实体类的新实例中。.create()
当它们不存在时,它似乎也会创建承诺。这实际上可能是导致此问题的原因;aCreated
在调用之前的日志记录aRepo.save(aCreated)
表明承诺没有解决。
事实上,删除aRepo.create(a)
步骤(并将 save 更改为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
关系(即),并从onb
构建一个空的。新的没有定义任何属性,因为实例不共享任何属性。然后因为没有指定,所以没有为 定义外键名和值,导致你遇到的错误。B
Promise
a.b
B
Promise
B
id
aRepo.save()
因此,在这种情况下,简单地a
直接传递aRepo.save()
并删除该aRepo.create(a)
步骤似乎是正确的做法。
然而,这是一个应该解决的问题——但我认为这不是一个容易解决的问题,因为这确实需要Repository.create()
能够实现await
Promise;目前无法实现,因为Repository.create()
它不是异步的。
推荐阅读
- sql-server - 将 WHERE 子句转换为 CASE 表达式
- ios - 如何解析字符串以获取测量值(与MeasurementFormatter相反)
- amazon-web-services - AWSDeploy 重新部署 ASP.NET WebAPI ELB 应用程序不起作用
- automapper - AutoMapper 如何映射 CamelCase 字典
到 PascalCase C# 对象 - python - python递归关系如何进行明确的循环并逐行打印所有结果
- python - 如何按属性值对字典进行排序?
- ionic3 - 调用 Cloud Firestore API
- postgresql - PostgreSQL 查询:需要在 NOW() 中添加 7 小时以调整时间到 GMT 以匹配我计算的另一侧
- python - 使用python按顺序创建、执行和删除docker镜像
- sql - 将 BLOB 转换为 XML - XML 解析:非法 xml 字符