|
1 | 1 | public partial class IntegrationTests |
2 | 2 | { |
3 | 3 | /// <summary> |
4 | | - /// Test that explicit projection accessing properties through abstract navigation throws. |
5 | | - /// DerivedChildEntity.Parent is BaseEntity (abstract), so projecting Parent.Property |
6 | | - /// should throw because abstract types cannot be projected by EF Core. |
| 4 | + /// Test that explicit projection accessing properties through abstract navigation succeeds. |
| 5 | + /// DerivedChildEntity.Parent is BaseEntity (abstract), but explicit projections that extract |
| 6 | + /// specific scalar properties are ALLOWED because EF Core can project scalar properties |
| 7 | + /// through abstract navigations efficiently. Only identity projections are problematic. |
7 | 8 | /// </summary> |
8 | 9 | [Fact] |
9 | | - public void Explicit_projection_with_abstract_navigation_property_throws() |
| 10 | + public void Explicit_projection_with_abstract_navigation_property_succeeds() |
10 | 11 | { |
11 | 12 | var filters = new Filters<IntegrationDbContext>(); |
12 | 13 |
|
13 | | - var exception = Assert.Throws<InvalidOperationException>(() => |
14 | | - { |
15 | | - // Projection accesses Parent.Property where Parent is BaseEntity (abstract) |
16 | | - filters.For<DerivedChildEntity>().Add( |
17 | | - projection: c => c.Parent!.Property, |
18 | | - filter: (_, _, _, prop) => prop == "test"); |
19 | | - }); |
| 14 | + // Projection accesses Parent.Property where Parent is BaseEntity (abstract) |
| 15 | + // This is ALLOWED - EF Core generates efficient JOIN to get only the needed column |
| 16 | + filters.For<DerivedChildEntity>().Add( |
| 17 | + projection: c => c.Parent!.Property, |
| 18 | + filter: (_, _, _, prop) => prop == "test"); |
20 | 19 |
|
21 | | - Assert.Contains("abstract navigation", exception.Message, StringComparison.OrdinalIgnoreCase); |
22 | | - Assert.Contains("Parent", exception.Message); |
| 20 | + // Verify the filter was registered with the expected property |
| 21 | + var requiredProps = filters.GetRequiredFilterProperties<DerivedChildEntity>(); |
| 22 | + Assert.Contains("Parent.Property", requiredProps); |
23 | 23 | } |
24 | 24 |
|
25 | 25 | /// <summary> |
26 | | - /// Test that anonymous type projection with abstract navigation also throws. |
27 | | - /// Even with anonymous types, we cannot project from abstract navigations. |
| 26 | + /// Test that anonymous type projection with abstract navigation succeeds. |
| 27 | + /// Explicit projections (including anonymous types) that extract specific properties |
| 28 | + /// through abstract navigations are ALLOWED - EF Core handles this efficiently. |
28 | 29 | /// </summary> |
29 | 30 | [Fact] |
30 | | - public void Anonymous_type_projection_with_abstract_navigation_throws() |
| 31 | + public void Anonymous_type_projection_with_abstract_navigation_succeeds() |
31 | 32 | { |
32 | 33 | var filters = new Filters<IntegrationDbContext>(); |
33 | 34 |
|
34 | | - var exception = Assert.Throws<InvalidOperationException>(() => |
35 | | - { |
36 | | - // Even with anonymous type, projecting from abstract Parent throws |
37 | | - filters.For<DerivedChildEntity>().Add( |
38 | | - projection: c => new { c.Id, ParentProperty = c.Parent!.Property }, |
39 | | - filter: (_, _, _, proj) => proj.ParentProperty == "test"); |
40 | | - }); |
| 35 | + // Anonymous type projection extracting properties through abstract Parent |
| 36 | + // This is ALLOWED - EF Core generates efficient SQL with JOINs |
| 37 | + filters.For<DerivedChildEntity>().Add( |
| 38 | + projection: c => new { c.Id, ParentProperty = c.Parent!.Property }, |
| 39 | + filter: (_, _, _, proj) => proj.ParentProperty == "test"); |
41 | 40 |
|
42 | | - Assert.Contains("abstract navigation", exception.Message, StringComparison.OrdinalIgnoreCase); |
43 | | - Assert.Contains("Parent", exception.Message); |
| 41 | + // Verify the filter was registered with expected properties |
| 42 | + var requiredProps = filters.GetRequiredFilterProperties<DerivedChildEntity>(); |
| 43 | + Assert.Contains("Id", requiredProps); |
| 44 | + Assert.Contains("Parent.Property", requiredProps); |
44 | 45 | } |
45 | 46 |
|
46 | 47 | /// <summary> |
@@ -104,22 +105,43 @@ public void Four_parameter_filter_with_abstract_navigation_allowed_at_runtime() |
104 | 105 | } |
105 | 106 |
|
106 | 107 | /// <summary> |
107 | | - /// Test projecting from nested abstract navigation property. |
| 108 | + /// Test projecting from nested abstract navigation property succeeds. |
| 109 | + /// Explicit projections that extract specific scalar properties are allowed. |
| 110 | + /// </summary> |
| 111 | + [Fact] |
| 112 | + public void Projection_with_nested_abstract_navigation_property_succeeds() |
| 113 | + { |
| 114 | + var filters = new Filters<IntegrationDbContext>(); |
| 115 | + |
| 116 | + // Accessing nested property through abstract parent is allowed for explicit projections |
| 117 | + filters.For<DerivedChildEntity>().Add( |
| 118 | + projection: c => c.Parent!.Status, |
| 119 | + filter: (_, _, _, status) => status == "Active"); |
| 120 | + |
| 121 | + var requiredProps = filters.GetRequiredFilterProperties<DerivedChildEntity>(); |
| 122 | + Assert.Contains("Parent.Status", requiredProps); |
| 123 | + } |
| 124 | + |
| 125 | + /// <summary> |
| 126 | + /// Test that identity projection with filter accessing abstract navigation is NOT caught at runtime. |
| 127 | + /// The runtime cannot analyze the filter delegate. The GQLEF007 analyzer catches this at compile time. |
108 | 128 | /// </summary> |
109 | 129 | [Fact] |
110 | | - public void Projection_with_nested_abstract_navigation_property_throws() |
| 130 | + public void Identity_projection_with_abstract_navigation_in_filter_not_caught_at_runtime() |
111 | 131 | { |
112 | 132 | var filters = new Filters<IntegrationDbContext>(); |
113 | 133 |
|
114 | | - var exception = Assert.Throws<InvalidOperationException>(() => |
115 | | - { |
116 | | - // Accessing nested property through abstract parent |
117 | | - filters.For<DerivedChildEntity>().Add( |
118 | | - projection: c => c.Parent!.Status, |
119 | | - filter: (_, _, _, status) => status == "Active"); |
120 | | - }); |
| 134 | + // Identity projection where filter accesses abstract Parent |
| 135 | + // This is NOT caught at runtime because: |
| 136 | + // 1. The projection `c => c` has no property accesses to analyze |
| 137 | + // 2. The filter is a compiled delegate, not an expression tree |
| 138 | + // The GQLEF007 analyzer catches this at compile time instead. |
| 139 | + filters.For<DerivedChildEntity>().Add( |
| 140 | + projection: c => c, |
| 141 | + filter: (_, _, _, c) => c.Parent!.Property == "test"); |
121 | 142 |
|
122 | | - Assert.Contains("abstract navigation", exception.Message, StringComparison.OrdinalIgnoreCase); |
123 | | - Assert.Contains("Parent", exception.Message); |
| 143 | + // Identity projection extracts no properties |
| 144 | + var requiredProps = filters.GetRequiredFilterProperties<DerivedChildEntity>(); |
| 145 | + Assert.Empty(requiredProps); |
124 | 146 | } |
125 | 147 | } |
0 commit comments