Skip to content

Commit 9061489

Browse files
authored
Merge pull request #52 from EFNext/fix/issue-50
fix: address issue #50 follow-ups (non-public properties, IQueryable upcasts)
2 parents c780a01 + 823d786 commit 9061489

280 files changed

Lines changed: 612 additions & 533 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,10 @@ private string EmitConversion(IConversionOperation conversion)
10051005
return quoteResult;
10061006
}
10071007

1008+
// Implicit reference upcasts in argument position break EF Core's queryable-chain matching.
1009+
if (conversion.IsImplicit && conversion.Conversion.IsReference && conversion.Parent is IArgumentOperation)
1010+
return EmitOperation(conversion.Operand);
1011+
10081012
var resultVar = NextVar();
10091013
var operandVar = EmitOperation(conversion.Operand);
10101014
var targetTypeFqn = conversion.Type?.ToDisplayString(_fqnFormat) ?? "object";

src/ExpressiveSharp.Generator/Emitter/ReflectionFieldCache.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ private string ResolveTypeFqn(ITypeSymbol type)
2020
public string EnsurePropertyInfo(IPropertySymbol property)
2121
{
2222
var typeFqn = ResolveTypeFqn(property.ContainingType);
23-
return $"typeof({typeFqn}).GetProperty(\"{property.Name}\")";
23+
var flags = property.IsStatic
24+
? "global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static"
25+
: "global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance";
26+
return $"typeof({typeFqn}).GetProperty(\"{property.Name}\", {flags})";
2427
}
2528

2629
public string EnsureFieldInfo(IFieldSymbol field)
@@ -99,7 +102,8 @@ public string EnsureConstructorInfo(IMethodSymbol constructor)
99102
var typeFqn = ResolveTypeFqn(constructor.ContainingType);
100103
var paramTypes = string.Join(", ", constructor.Parameters.Select(p =>
101104
$"typeof({ResolveTypeFqn(p.Type)})"));
102-
return $"typeof({typeFqn}).GetConstructor(new global::System.Type[] {{ {paramTypes} }})";
105+
const string flags = "global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance";
106+
return $"typeof({typeFqn}).GetConstructor({flags}, null, new global::System.Type[] {{ {paramTypes} }}, null)";
103107
}
104108

105109
private static bool ContainsTypeParameter(ITypeSymbol type)

tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Tests/Sqlite/SynthesizedExpressiveSqlTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,30 @@ public async Task SynthesizedProperty_MemberInitProjection_MaterializesStoredVal
7171
Assert.AreEqual("Turing, Alan", projected[1].FullName);
7272
}
7373

74+
[TestMethod]
75+
public async Task SynthesizedProperty_BodyUsesStaticHelperIQueryable_TranslatesToCorrelatedSubquery()
76+
{
77+
// Issue #50 follow-up: [ExpressiveProperty] body uses IQueryable<T> from a static helper.
78+
GroupedRowQueryContext.Db = Context;
79+
Context.Rows.AddRange(
80+
new GroupedRow { Id = 1, GroupId = 1, CreatedAt = new DateTime(2026, 1, 1) },
81+
new GroupedRow { Id = 2, GroupId = 1, CreatedAt = new DateTime(2026, 1, 2) },
82+
new GroupedRow { Id = 3, GroupId = 1, CreatedAt = new DateTime(2026, 1, 3) },
83+
new GroupedRow { Id = 4, GroupId = 2, CreatedAt = new DateTime(2026, 1, 1) });
84+
await Context.SaveChangesAsync();
85+
86+
var pairs = await Context.Rows
87+
.OrderBy(r => r.Id)
88+
.Select(r => new { r.Id, r.PreviousId })
89+
.ToListAsync();
90+
91+
Assert.AreEqual(4, pairs.Count);
92+
Assert.AreEqual(0, pairs[0].PreviousId);
93+
Assert.AreEqual(1, pairs[1].PreviousId);
94+
Assert.AreEqual(2, pairs[2].PreviousId);
95+
Assert.AreEqual(0, pairs[3].PreviousId);
96+
}
97+
7498
[TestMethod]
7599
public async Task SynthesizedProperty_WhereClauseFiltersOnFormula()
76100
{
@@ -96,6 +120,7 @@ public partial class SynthesizedPerson
96120
public class SynthesizedDbContext(DbContextOptions<SynthesizedDbContext> options) : DbContext(options)
97121
{
98122
public DbSet<SynthesizedPerson> People => Set<SynthesizedPerson>();
123+
public DbSet<GroupedRow> Rows => Set<GroupedRow>();
99124

100125
protected override void OnModelCreating(ModelBuilder modelBuilder)
101126
{
@@ -104,5 +129,31 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
104129
entity.HasKey(e => e.Id);
105130
entity.Property(e => e.Id).ValueGeneratedNever();
106131
});
132+
modelBuilder.Entity<GroupedRow>(entity =>
133+
{
134+
entity.HasKey(e => e.Id);
135+
entity.Property(e => e.Id).ValueGeneratedNever();
136+
});
107137
}
108138
}
139+
140+
internal static class GroupedRowQueryContext
141+
{
142+
public static SynthesizedDbContext Db { get; set; } = null!;
143+
public static IQueryable<T> Query<T>() where T : class => Db.Set<T>();
144+
}
145+
146+
public partial class GroupedRow
147+
{
148+
public int Id { get; init; }
149+
public int GroupId { get; init; }
150+
public DateTime CreatedAt { get; init; }
151+
152+
[ExpressiveProperty("PreviousId")]
153+
private int PreviousIdExpr =>
154+
GroupedRowQueryContext.Query<GroupedRow>()
155+
.Where(r => r.GroupId == GroupId && r.CreatedAt < CreatedAt)
156+
.OrderByDescending(r => r.CreatedAt)
157+
.Select(r => r.Id)
158+
.FirstOrDefault();
159+
}

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/BlockBodyTests.BlockBodiedMethod_WithEarlyReturn.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace ExpressiveSharp.Generated
1717
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.C, string>> Foo_Expression()
1818
{
1919
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this");
20-
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
20+
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
2121
var expr_3 = global::System.Linq.Expressions.Expression.Constant(10, typeof(int)); // 10
2222
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_2, expr_3);
2323
var expr_4 = global::System.Linq.Expressions.Expression.Constant("Bulk", typeof(string)); // "Bulk"

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/BlockBodyTests.BlockBodiedMethod_WithEarlyReturn_AfterLocalVar.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace ExpressiveSharp.Generated
1919
{
2020
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this");
2121
var expr_0 = global::System.Linq.Expressions.Expression.Variable(typeof(int), "threshold"); // { var threshold = Bar * 10; if (t...
22-
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
22+
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
2323
var expr_3 = global::System.Linq.Expressions.Expression.Constant(10, typeof(int)); // 10
2424
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_2, expr_3);
2525
var expr_4 = global::System.Linq.Expressions.Expression.Assign(expr_0, expr_1);

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/BlockBodyTests.BlockBodiedMethod_WithIfElse.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace ExpressiveSharp.Generated
2222
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.C, int>> Foo_Expression()
2323
{
2424
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this");
25-
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
25+
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
2626
var expr_3 = global::System.Linq.Expressions.Expression.Constant(10, typeof(int)); // 10
2727
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_2, expr_3);
2828
var expr_4 = global::System.Linq.Expressions.Expression.Constant(1, typeof(int)); // 1

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/BlockBodyTests.BlockBodiedMethod_WithLocalVariable.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace ExpressiveSharp.Generated
1717
{
1818
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this");
1919
var expr_0 = global::System.Linq.Expressions.Expression.Variable(typeof(int), "temp"); // { var temp = Bar * 2; return temp...
20-
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
20+
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
2121
var expr_3 = global::System.Linq.Expressions.Expression.Constant(2, typeof(int)); // 2
2222
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_2, expr_3);
2323
var expr_4 = global::System.Linq.Expressions.Expression.Assign(expr_0, expr_1);

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/BlockBodyTests.BlockBodiedMethod_WithMultipleEarlyReturns.verified.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ namespace ExpressiveSharp.Generated
2121
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.C, string>> Foo_Expression()
2222
{
2323
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this");
24-
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
24+
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
2525
var expr_3 = global::System.Linq.Expressions.Expression.Constant(100, typeof(int)); // 100
2626
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_2, expr_3);
2727
var expr_4 = global::System.Linq.Expressions.Expression.Constant("Huge", typeof(string)); // "Huge"
28-
var expr_7 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
28+
var expr_7 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
2929
var expr_8 = global::System.Linq.Expressions.Expression.Constant(10, typeof(int)); // 10
3030
var expr_6 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_7, expr_8);
3131
var expr_9 = global::System.Linq.Expressions.Expression.Constant("Big", typeof(string)); // "Big"
32-
var expr_12 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
32+
var expr_12 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
3333
var expr_13 = global::System.Linq.Expressions.Expression.Constant(0, typeof(int)); // 0
3434
var expr_11 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_12, expr_13);
3535
var expr_14 = global::System.Linq.Expressions.Expression.Constant("Small", typeof(string)); // "Small"

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/BlockBodyTests.BlockBodiedMethod_WithNestedIfElse.verified.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ namespace ExpressiveSharp.Generated
2626
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.C, string>> Foo_Expression()
2727
{
2828
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this");
29-
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
29+
var expr_2 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
3030
var expr_3 = global::System.Linq.Expressions.Expression.Constant(10, typeof(int)); // 10
3131
var expr_1 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_2, expr_3);
3232
var expr_4 = global::System.Linq.Expressions.Expression.Constant("High", typeof(string)); // "High"
33-
var expr_7 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
33+
var expr_7 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
3434
var expr_8 = global::System.Linq.Expressions.Expression.Constant(5, typeof(int)); // 5
3535
var expr_6 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, expr_7, expr_8);
3636
var expr_9 = global::System.Linq.Expressions.Expression.Constant("Medium", typeof(string)); // "Medium"

tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/BlockBodyTests.BlockBodiedMethod_WithPropertyAccess.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace ExpressiveSharp.Generated
1515
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.C, int>> Foo_Expression()
1616
{
1717
var p__this = global::System.Linq.Expressions.Expression.Parameter(typeof(global::Foo.C), "@this");
18-
var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar")); // Bar
18+
var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Bar", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance)); // Bar
1919
var expr_2 = global::System.Linq.Expressions.Expression.Constant(10, typeof(int)); // 10
2020
var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Add, expr_1, expr_2);
2121
return global::System.Linq.Expressions.Expression.Lambda<global::System.Func<global::Foo.C, int>>(expr_0, p__this);

0 commit comments

Comments
 (0)