Skip to content

Commit c16c502

Browse files
committed
fix: handle tph during bulk save collection fixup
1 parent 59181b6 commit c16c502

File tree

11 files changed

+473
-64
lines changed

11 files changed

+473
-64
lines changed

.vscode/settings.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
{
2-
"dotnet.defaultSolution": "Coalesce.sln"
3-
}
2+
"dotnet.defaultSolution": "Coalesce.sln",
3+
4+
"editor.formatOnPaste": false,
5+
"editor.formatOnSave": true,
6+
"editor.formatOnType": true,
7+
"editor.defaultFormatter": "esbenp.prettier-vscode"
8+
}
9+

CHANGELOG.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
1-
# 5.3.5
1+
# 5.3.6
22

3-
## Fixes
3+
- Fix error in bulk saves where models that belong to inheritance hierarchies would get skipped over while performing collection fixup.
4+
5+
# 5.3.5
46

57
- Fix error in c-select-many-to-many when the near side is part of an inheritance hierarchy.
68

79
# 5.3.4
810

9-
## Fixes
10-
1111
- Adjust DateTimeOffset handling to support Postgres restrictions of not allowing non-zero offsets.
1212

1313
# 5.3.3
1414

15-
## Fixes
16-
1715
- Add model validation error when ForeignKeyAttribute references a navigation prop that doesn't exist.
1816
- Fix code gen performance regression introduced in 5.3.2
1917

2018
# 5.3.2
2119

22-
## Fixes
23-
2420
- Fix error in .NET 9 thrown by vite development middleware if the HTTPS cert directory doesn't exist.
2521
- Fix `ViewModel.$load` and `ListViewModel.$load` not properly working with `.useResponseCaching()`.
2622
- Entities that own multiple one-to-one relationships should no longer throw errors when generating.
@@ -30,8 +26,6 @@
3026

3127
# 5.3.1
3228

33-
## Fixes
34-
3529
- Fix error thrown by `CoalesceApiDescriptionProvider` when a custom method on a model or service has an explicitly `[Inject]`ed data source parameter.
3630

3731
# 5.3.0

src/IntelliTect.Coalesce.Tests/TargetClasses/TestDbContext/AbstractModel.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using IntelliTect.Coalesce.DataAnnotations;
2+
using IntelliTect.Coalesce.Tests.TargetClasses.TestDbContext;
3+
using System.Collections.Generic;
24

35
namespace IntelliTect.Coalesce.Tests.TargetClasses
46
{
@@ -10,12 +12,32 @@ public abstract class AbstractModel
1012
{
1113
public int Id { get; set; }
1214

13-
public string Discriminatior { get; set; }
15+
public string Discriminator { get; set; }
16+
17+
[ManyToMany("People")]
18+
public List<AbstractModelPerson> AbstractModelPeople { get; set; }
19+
}
20+
21+
[Edit(PermissionLevel = SecurityPermissionLevels.DenyAll)]
22+
public class AbstractImpl1 : AbstractModel
23+
{
24+
public string Impl1OnlyField { get; set; }
1425
}
1526

1627
[Edit(PermissionLevel = SecurityPermissionLevels.DenyAll)]
17-
public class AbstractImpl : AbstractModel
28+
public class AbstractImpl2 : AbstractModel
1829
{
19-
public string ImplOnlyField { get; set; }
30+
public string Impl2OnlyField { get; set; }
31+
}
32+
33+
public class AbstractModelPerson
34+
{
35+
public int Id { get; set; }
36+
37+
public int PersonId { get; set; }
38+
public Person Person { get; set; }
39+
40+
public int AbstractModelId { get; set; }
41+
public AbstractModel AbstractModel { get; set; }
2042
}
2143
}

src/IntelliTect.Coalesce.Tests/TargetClasses/TestDbContext/TestDbContext.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public class AppDbContext : DbContext
2424
public DbSet<Test> Tests { get; set; }
2525

2626
public DbSet<AbstractModel> AbstractModels { get; set; }
27-
public DbSet<AbstractImpl> AbstractImpls { get; set; }
27+
public DbSet<AbstractModelPerson> AbstractModelPeople { get; set; }
28+
public DbSet<AbstractImpl1> AbstractImpl1s { get; set; }
29+
public DbSet<AbstractImpl2> AbstractImpl2s { get; set; }
2830

2931
public DbSet<EnumPk> EnumPks { get; set; }
3032
public DbSet<ZipCode> ZipCodes { get; set; }
@@ -60,8 +62,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
6062
base.OnModelCreating(modelBuilder);
6163

6264
modelBuilder.Entity<AbstractModel>()
63-
.HasDiscriminator(b => b.Discriminatior)
64-
.HasValue<AbstractImpl>("impl");
65+
.HasDiscriminator(b => b.Discriminator)
66+
.HasValue<AbstractImpl1>("impl1")
67+
.HasValue<AbstractImpl2>("impl2");
6568
}
6669
}
6770

src/IntelliTect.Coalesce.Tests/Tests/TypeDefinition/ClassViewModelTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void DefaultOrderBy_UsesNavigationDirectlyWhenOrderingByUnorderableRefNav
4444
}
4545

4646
[Theory]
47-
[ClassViewModelData(typeof(AbstractImpl))]
47+
[ClassViewModelData(typeof(AbstractImpl1))]
4848
public void GetAttribute_RespectsInheritance(ClassViewModelData data)
4949
{
5050
var vm = data.ClassViewModel;

src/coalesce-vue/src/viewmodel.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,12 @@ export abstract class ViewModel<
744744
if (
745745
!principal &&
746746
collection?.$parent instanceof ViewModel &&
747-
collection.$metadata == prop.inverseNavigation
747+
(collection.$metadata == prop.inverseNavigation ||
748+
// Handle inheritance hierarchies (i.e. TPH):
749+
// The collection is the same collection, but has a different metadata instance
750+
// so direct equality won't work.
751+
(collection.$metadata.name == prop.inverseNavigation?.name &&
752+
collection.$metadata.type == prop.inverseNavigation.type))
748753
) {
749754
// The reference navigation property has no value,
750755
// and the foreign key has no value,

src/coalesce-vue/test/viewmodel.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
ProductViewModel,
3838
TestViewModel,
3939
ZipCodeViewModel,
40+
AbstractImpl1ViewModel,
4041
} from "../../test-targets/viewmodels.g";
4142
import {
4243
StudentViewModel,
@@ -1402,6 +1403,52 @@ describe("ViewModel", () => {
14021403

14031404
bulkSaveEndpoint.destroy();
14041405
});
1406+
1407+
test("creation of TPH entity with many-to-many that references base type", async () => {
1408+
const bulkSaveEndpoint = mockEndpoint(
1409+
"/AbstractImpl1/bulkSave",
1410+
vitest.fn((req) => ({
1411+
wasSuccessful: true,
1412+
object: null,
1413+
}))
1414+
);
1415+
1416+
// This tests the fact that the metadata instance of
1417+
// `AbstractImpl1ViewModel.abstractModelPeople.$metadata` is the one owned by `AbstractImpl1`'s metadata,
1418+
// but the metadata instance of `AbstractModelPerson.$metadata.props.abstractModel.inverseNavigation`
1419+
// is the copy of the same collection that is owned by `AbstractModel`'s metadata.
1420+
//
1421+
// The collection navigation fixup logic in bulk saves has to allow for this type to be different.
1422+
1423+
const person = new PersonViewModel();
1424+
person.$loadCleanData({ personId: 1, name: "bob" });
1425+
const vm = new AbstractImpl1ViewModel({ impl1OnlyField: "fieldval" });
1426+
vm.abstractModelPeople.push({ person });
1427+
await vm.$bulkSave();
1428+
1429+
expect(JSON.parse(bulkSaveEndpoint.mock.calls[0][0].data)).toMatchObject({
1430+
items: [
1431+
{
1432+
action: "save",
1433+
type: "AbstractImpl1",
1434+
data: { id: null, impl1OnlyField: "fieldval" },
1435+
refs: { id: vm.$stableId },
1436+
root: true,
1437+
},
1438+
{
1439+
action: "save",
1440+
type: "AbstractModelPerson",
1441+
data: { id: null },
1442+
refs: {
1443+
id: vm.abstractModelPeople[0].$stableId,
1444+
abstractModelId: vm.$stableId,
1445+
},
1446+
},
1447+
],
1448+
});
1449+
1450+
bulkSaveEndpoint.destroy();
1451+
});
14051452
});
14061453

14071454
describe("autoSave", () => {

src/test-targets/api-clients.g.ts

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)