Skip to content

Add partial class support for private/protected/internal member access in [Projectable] members#159

Draft
Copilot wants to merge 3 commits intomasterfrom
copilot/add-protected-method-support
Draft

Add partial class support for private/protected/internal member access in [Projectable] members#159
Copilot wants to merge 3 commits intomasterfrom
copilot/add-protected-method-support

Conversation

Copy link
Contributor

Copilot AI commented Mar 1, 2026

Generated expression classes live in EntityFrameworkCore.Projectables.Generated, making private and protected members of the declaring type inaccessible. When the declaring class (and all nesting ancestors) are partial, the generated class can instead be nested inside the declaring type, granting access to all its members.

How it works

When all containing types are partial, the generator emits the expression class as a nested static class inside the declaring type rather than in the Generated namespace:

// Before: generated in EntityFrameworkCore.Projectables.Generated — cannot access _value
public class Order {
    private decimal _taxRate;
    [Projectable] public decimal Tax => Subtotal * _taxRate; // ❌ inaccessible from generated class
}

// After: mark the class partial and the generated code nests inside it
public partial class Order {
    private decimal _taxRate;
    [Projectable] public decimal Tax => Subtotal * _taxRate; // ✅ nested class can access _taxRate
}

Private [Projectable] helpers called from other [Projectable] members also work:

public partial class Order {
    [Projectable] private decimal ApplyTax(decimal amount) => amount * _taxRate;
    [Projectable] public decimal Total => ApplyTax(Subtotal);
}

internal members remain accessible without partial — they're in the same assembly as the generated class.

Changes

  • ProjectableDescriptor – new IsContainingClassPartial + ContainingTypeChain to carry partial class info to the generator.
  • ProjectableInterpreter
    • Detects when all containing types are partial and captures their TypeDeclarationSyntax chain.
    • Emits new EFP0007 warning when a [Projectable] member accesses private/protected/private protected members but the class is not partial (lambda nodes and object-level protected methods excluded to avoid false positives).
  • ProjectionExpressionGenerator – when partial, wraps the generated static class inside the chain of partial type declarations in the original namespace (no type parameters redeclared — they're inherited). Also fixes a pre-existing duplicate using directive bug.
  • ProjectionExpressionResolver – falls back to declaringType.GetNestedType(generatedClassName) when the standard Generated namespace lookup returns null.
  • Diagnostics.cs + AnalyzerReleases.Unshipped.md – diagnostic EFP0007.
Original prompt

This section details on the original issue you should resolve

<issue_title>Protected/private property/method support</issue_title>
<issue_description>Is supporting the use of protected/private methods/members in a projected expression something that can be added?</issue_description>

<agent_instructions>Implement the first suggestion : for class members (not extensions) if the host class is partial, implement the generated expressions as a partial, add tests and functional test showing it work for private, protected and internal accessibility.
If the class is not partial and if you can easily detect access to a inaccessible member, report a warning suggesting to make class partial.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@koenbeuk Expression trees are generated for projectable methods as part of an external class. A projectable expression is simply a rewrite of the original implementation. If this refers to a private member then that is not accessible from the external class. There are 2 workarounds possible:
  1. We generate expressions as part of a partial class. e.g., when you declare the container class of your projectables as Partial, we can generate the projectable methods within the same class and hence those generated projectable methods will have access to the internals of that class.

  2. We rewrite the generated expression to basically inline the logic of the internal member that it's referring to, e.g.

class Foo {
  [Projectable] private int Sum(int a, int b) => a + b;
  [Projectable] public int SumAll(int[] numbers) => numbers.Aggregate((next, total) => Sum(total, next), 0);
 }
 
 // Generated code:
class Generated_SumAll {
  // Before
  public Expression<Func<int[], int>> Expression = (int[] numbers) => numbers.Aggregate((next, total) => Foo.Sum(total, next), 0)

  // After
  public Expression<Func<int[], int>> Expression = (int[] numbers) => numbers.Aggregate((next, total) => total +  next, 0);
}

The downside of the second approach is that it would not be able to generate code for projectables that are part of a type that lives in a different assembly as it would not have access to the syntax tree for that method.

Either way, this could be implemented. Happy to accept a PR here.</comment_new>


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits March 1, 2026 14:34
…jectable] members

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
…ning

Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for protected/private methods in projected expressions Add partial class support for private/protected/internal member access in [Projectable] members Mar 1, 2026
Copilot finished work on behalf of PhenX March 1, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Protected/private property/method support

2 participants