fix: prevent eager-loaded entities from overwriting manual relations (#11267)

Co-authored-by: Lucian Mocanu <alumni@users.noreply.github.com>
Co-authored-by: Giorgio Boa <35845425+gioboa@users.noreply.github.com>
This commit is contained in:
LeviHeber 2025-12-05 15:19:02 +02:00 committed by GitHub
parent 6e34756b9d
commit 2d8c5158db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 14 deletions

View File

@ -12,6 +12,7 @@ import { PropertyTypeFactory } from "./types/PropertyTypeInFunction"
import { TypeORMError } from "../error"
import { ObjectUtils } from "../util/ObjectUtils"
import { InstanceChecker } from "../util/InstanceChecker"
import { OrmUtils } from "../util/OrmUtils"
/**
* Contains all information about some entity's relation.
@ -520,7 +521,11 @@ export class RelationMetadata {
entity,
)
} else {
entity[propertyName] = value
if (ObjectUtils.isObject(entity[propertyName])) {
OrmUtils.mergeDeep(entity[propertyName], value)
} else {
entity[propertyName] = value
}
}
}

View File

@ -1,3 +1,4 @@
import { expect } from "chai"
import "reflect-metadata"
import { DataSource } from "../../../../../src/data-source/DataSource"
import {
@ -5,11 +6,11 @@ import {
createTestingConnections,
reloadTestingDatabases,
} from "../../../../utils/test-utils"
import { User } from "./entity/User"
import { Profile } from "./entity/Profile"
import { Category } from "./entity/Category"
import { Editor } from "./entity/Editor"
import { Post } from "./entity/Post"
import { Category } from "./entity/Category"
import { Profile } from "./entity/Profile"
import { User } from "./entity/User"
describe("relations > eager relations > basic", () => {
let connections: DataSource[]
@ -77,7 +78,7 @@ describe("relations > eager relations > basic", () => {
loadedPost!.categories1.sort((a, b) => a.id - b.id)
loadedPost!.categories2.sort((a, b) => a.id - b.id)
loadedPost!.should.be.eql({
expect(loadedPost).to.deep.equal({
id: 1,
title: "about eager relations",
categories1: [
@ -104,6 +105,7 @@ describe("relations > eager relations > basic", () => {
id: 1,
firstName: "Timber",
lastName: "Saw",
deletedAt: null,
profile: {
id: 1,
about: "I cut trees!",
@ -117,6 +119,7 @@ describe("relations > eager relations > basic", () => {
id: 1,
firstName: "Timber",
lastName: "Saw",
deletedAt: null,
profile: {
id: 1,
about: "I cut trees!",
@ -138,10 +141,61 @@ describe("relations > eager relations > basic", () => {
.where("post.id = :id", { id: 1 })
.getOne()
loadedPost!.should.be.eql({
expect(loadedPost).to.deep.equal({
id: 1,
title: "about eager relations",
})
}),
))
it("should preserve manually requested nested relations with DeleteDateColumn", () =>
Promise.all(
connections.map(async (connection) => {
await prepareData(connection)
// Prepare test data - reusing existing entities
const nestedProfile = new Profile()
nestedProfile.about = "I am nested!"
await connection.manager.save(nestedProfile)
const user = (await connection.manager.findOne(User, {
where: { id: 1 },
}))!
user.nestedProfile = nestedProfile
await connection.manager.save(user)
// Retrieve user with manually specified nested relation
const retrievedEditor = await connection.manager.findOne(
Editor,
{
where: { userId: 1 },
relations: {
user: {
nestedProfile: true,
},
},
},
)
// Assertions
expect(retrievedEditor).to.deep.equal({
userId: 1,
postId: 1,
user: {
id: 1,
firstName: "Timber",
lastName: "Saw",
deletedAt: null,
nestedProfile: {
id: 2,
about: "I am nested!",
},
profile: {
id: 1,
about: "I cut trees!",
},
},
})
}),
))
})

View File

@ -1,6 +1,4 @@
import { Entity } from "../../../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../../../src/decorator/columns/Column"
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../../../src"
@Entity()
export class Profile {

View File

@ -1,8 +1,12 @@
import { Entity } from "../../../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../../../src/decorator/columns/Column"
import { OneToOne } from "../../../../../../src/decorator/relations/OneToOne"
import { JoinColumn } from "../../../../../../src/decorator/relations/JoinColumn"
import {
Column,
DeleteDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
} from "../../../../../../src"
import { Profile } from "./Profile"
@Entity()
@ -19,4 +23,10 @@ export class User {
@OneToOne(() => Profile, { eager: true })
@JoinColumn()
profile: Profile
@DeleteDateColumn()
deletedAt?: Date
@ManyToOne(() => Profile)
nestedProfile: Profile
}