Skip to content

Commit b9cb724

Browse files
dmealingclaude
andcommitted
test(csharp): EF Core compile-check for generated AppDbContext
Compile the generated AppDbContext + entity/value-object POCOs together against real EF Core 8 assemblies (in-memory Roslyn) so API mismatches like .ToJson() on a PropertyBuilder<List<T>> are caught at test time. Exercises the full DbContext EF surface: scalar enum (.HasConversion), array enum (.PrimitiveCollection().ElementType().HasConversion), scalar array (.PrimitiveCollection), flattened owned type (.OwnsOne + .HasColumnName), JSON owned type (.OwnsOne().ToJson), and dbView projection (.ToView). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 40a6c87 commit b9cb724

2 files changed

Lines changed: 146 additions & 0 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// EF Core compile-check for generated AppDbContext.
2+
//
3+
// Compiles the generated entity POCOs + AppDbContext TOGETHER against real
4+
// EF Core 8 assemblies (in-memory Roslyn, same pattern as EntityGeneratorTests).
5+
// This catches API mismatches that pure string-contains checks can't find —
6+
// e.g. calling .ToJson() on a PropertyBuilder<List<T>>, which EF Core 8 does not
7+
// expose (CS1061), even though the string appears valid.
8+
//
9+
// The fixture exercises the full EF surface in one model:
10+
// - object.value Address (owned type target)
11+
// - object.entity Order with:
12+
// scalar enum "status" → .HasConversion<string>()
13+
// array enum "statuses" (isArray) → .PrimitiveCollection().ElementType().HasConversion<string>()
14+
// array string "tags" (isArray) → .PrimitiveCollection()
15+
// field.object homeAddress @storage flattened → OwnsOne(...) per-property column names
16+
// field.object config (default storage) → OwnsOne(...).ToJson(...)
17+
// - object.entity ProgramSummary with source.dbView → .ToView(...) (no HasNoKey — keyed)
18+
//
19+
// The test DOES NOT include RoutesGenerator output: routes import ASP.NET Core
20+
// shared-framework types that are not available in the TRUSTED_PLATFORM_ASSEMBLIES
21+
// sandbox used by in-memory Roslyn tests.
22+
23+
using Microsoft.CodeAnalysis;
24+
using Microsoft.CodeAnalysis.CSharp;
25+
using MetaObjects.Codegen;
26+
using MetaObjects.Codegen.Generators;
27+
using MetaObjects.Loader;
28+
using MetaObjects.Meta;
29+
using Xunit;
30+
31+
namespace MetaObjects.Codegen.Tests;
32+
33+
public class DbContextCompileTests
34+
{
35+
// One model that exercises every EF-surface code-path in DbContextGenerator:
36+
// • scalar enum → .HasConversion<string>()
37+
// • array enum → .PrimitiveCollection().ElementType().HasConversion<string>()
38+
// • scalar array → .PrimitiveCollection()
39+
// • flattened object field → OwnsOne per-property column config
40+
// • json object field → OwnsOne(...).ToJson(...)
41+
// • dbView projection → .ToView(...)
42+
private const string Model = """
43+
{ "metadata.root": { "package": "acme", "children": [
44+
{ "object.value": { "name": "Address", "children": [
45+
{ "field.string": { "name": "street", "@required": true, "@maxLength": 120 } },
46+
{ "field.string": { "name": "city", "@maxLength": 80 } }
47+
]}},
48+
{ "object.entity": { "name": "Order", "children": [
49+
{ "source.dbTable": { "@name": "orders" } },
50+
{ "field.long": { "name": "id" } },
51+
{ "field.enum": { "name": "status", "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },
52+
{ "field.enum": { "name": "statuses", "isArray": true, "@values": ["DRAFT", "PUBLISHED", "ARCHIVED"] } },
53+
{ "field.string": { "name": "tags", "isArray": true } },
54+
{ "field.object": { "name": "homeAddress", "@objectRef": "Address", "@storage": "flattened" } },
55+
{ "field.object": { "name": "config", "@objectRef": "Address" } },
56+
{ "identity.primary": { "@fields": "id" } }
57+
]}},
58+
{ "object.entity": { "name": "ProgramSummary", "children": [
59+
{ "source.dbView": { "@name": "v_program_summary" } },
60+
{ "field.long": { "name": "id" } },
61+
{ "field.int": { "name": "weekCount" } },
62+
{ "identity.primary": { "@fields": "id" } }
63+
]}}
64+
]}}
65+
""";
66+
67+
private static MetaRoot Load()
68+
{
69+
var r = new MetaDataLoader().Load([new InMemorySource(Model, id: "ef-compile.json")]);
70+
Assert.Empty(r.Errors);
71+
return r.Root;
72+
}
73+
74+
private static GenContext Ctx(MetaRoot root) => new()
75+
{
76+
Entities = root.Objects(), Root = root,
77+
Config = new GenConfig { OutDir = "/tmp", Namespace = "Acme.Generated" },
78+
};
79+
80+
// Build a MetadataReference list = TRUSTED_PLATFORM_ASSEMBLIES + EF Core assemblies.
81+
// TRUSTED_PLATFORM_ASSEMBLIES already includes the BCL and EF Core may or may not be
82+
// listed there depending on the test runner; using typeof(...).Assembly.Location and
83+
// deduplicating by path is the safe approach.
84+
private static List<MetadataReference> BuildReferences()
85+
{
86+
var paths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
87+
88+
// BCL + SDK assemblies from the trusted platform.
89+
var tpa = (string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!;
90+
foreach (var p in tpa.Split(Path.PathSeparator))
91+
if (p.Length > 0) paths.Add(p);
92+
93+
// EF Core 8 assemblies — always add explicitly so the reference is present
94+
// even if the test runner hasn't put them in TRUSTED_PLATFORM_ASSEMBLIES.
95+
// The core package supplies DbContext / ModelBuilder / EntityTypeBuilder.
96+
// The relational package supplies the extension methods used in OnModelCreating:
97+
// .ToView(), .HasColumnName(), .ToJson(), .HasConversion(), .PrimitiveCollection().
98+
paths.Add(typeof(Microsoft.EntityFrameworkCore.DbContext).Assembly.Location);
99+
paths.Add(typeof(Microsoft.EntityFrameworkCore.ModelBuilder).Assembly.Location);
100+
paths.Add(typeof(Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions).Assembly.Location);
101+
102+
return paths
103+
.Where(p => File.Exists(p))
104+
.Select(p => (MetadataReference)MetadataReference.CreateFromFile(p))
105+
.ToList();
106+
}
107+
108+
[Fact]
109+
public void Generated_AppDbContext_and_entities_compile_against_EF_Core_8()
110+
{
111+
var ctx = Ctx(Load());
112+
var refs = BuildReferences();
113+
114+
// Generate ALL files: entity POCOs + value-object POCOs + AppDbContext.
115+
// Routes are excluded — ASP.NET Core types are outside the sandbox.
116+
var entityFiles = new EntityGenerator().Generate(ctx).ToList();
117+
var dbContextFiles = new DbContextGenerator().Generate(ctx).ToList();
118+
119+
var allSources = entityFiles.Concat(dbContextFiles).ToList();
120+
Assert.NotEmpty(allSources);
121+
122+
var trees = allSources
123+
.Select(f => CSharpSyntaxTree.ParseText(
124+
f.Content,
125+
new CSharpParseOptions(LanguageVersion.CSharp12)))
126+
.ToList();
127+
128+
var comp = CSharpCompilation.Create(
129+
"efcore_compile_" + Guid.NewGuid().ToString("N"),
130+
trees,
131+
refs,
132+
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
133+
134+
var errors = comp.GetDiagnostics()
135+
.Where(d => d.Severity == DiagnosticSeverity.Error)
136+
.Select(d => $"{d.Id}: {d.GetMessage()}")
137+
.ToList();
138+
139+
Assert.True(
140+
errors.Count == 0,
141+
"Generated entity + AppDbContext should compile against EF Core 8, but got errors:\n"
142+
+ string.Join("\n", errors));
143+
}
144+
}

server/csharp/MetaObjects.Codegen.Tests/MetaObjects.Codegen.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<IsPackable>false</IsPackable>
77
</PropertyGroup>
88
<ItemGroup>
9+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
911
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
1012
<PackageReference Include="xunit" Version="2.9.2" />
1113
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />

0 commit comments

Comments
 (0)