Обновление lazyload родительского типа не удается при сохранении дочернего элемента - PullRequest
0 голосов
/ 29 августа 2018

Я не уверен, является ли это ошибкой, или я делаю что-то не так, но я пытался многое сделать, чтобы это работало, и я не мог. Я надеюсь, что вы, ребята, можете помочь.

В основном у меня есть отношения один к одному, которые мне нужны для 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

1 Ответ

0 голосов
/ 05 сентября 2018

Я прокололся в вашей ветке promise-issue хранилище и обнаружил несколько интересных вещей:

  1. Недопустимый запрос UPDATE инициируется начальным await aRepo.save(aCreated);, НЕ вставкой B и последующим присвоением внешнего ключа a.b. Назначение a.b = null до aRepo.create(a) позволяет избежать этой проблемы.

  2. Добавление инициализации 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);
  3. Я вполне уверен, что использование функций async для аргумента inverseSide для @OneToOne() (т. Е. async (o: B) => await o.a)) неверно.
    Документация указывает, что это должно быть просто (o: B) => o.a, и обобщения в OneToOne также подтверждают это.
    TypeORM выполнит обещание ДО того, как передаст значение этой функции, а функция async вернет другую Promise вместо правильного значения свойства.

  4. Я также только что заметил, что вы передаете экземпляр 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 для этого; Я посмотрю на создание тестового примера для демонстрации в течение следующих нескольких дней.

Надеюсь, этого достаточно, чтобы ответить на ваш вопрос!

Обновление

После некоторой дальнейшей трассировки кода выясняется, что причиной этой проблемы является элемент 4) из приведенного выше списка.

В RelationLoader.enableLazyLoad() TypeORM перегружает ленивые методы доступа к свойствам в экземплярах @Entity с помощью своих собственных методов получения и установки - например, Object.defineProperty(A, 'b', ...). Перегруженные средства доступа к свойствам загружают и кэшируют связанную запись B, возвращая Promise<B>.

Repository.create() выполняет итерацию всех отношений для созданной сущности и - когда объект предоставляется - строит новый связанный объект из предоставленных значений. Однако эта логика не учитывает объект 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() не является асинхронным.

...