Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c4d8bfa
Support explicitly implemented interface members
Rhodonvt Jun 25, 2025
a142c85
Support default interface members
Rhodonvt Jun 25, 2025
8a82bf8
Initial plan
Copilot Nov 23, 2025
f0c5a21
Initial plan
Copilot Nov 23, 2025
e686b23
Fix GetImplementingProperty to handle explicit interface implementations
Copilot Nov 23, 2025
201cabe
Add functional and generator tests for explicit interface implementat…
Copilot Nov 23, 2025
63977ca
Add parameter types to generated class names for overload support
Copilot Nov 23, 2025
b7cf397
Fix generic type parameter handling and arity marker positioning
Copilot Nov 23, 2025
593aa35
Add functional tests for method overloads
Copilot Nov 23, 2025
4de1f8b
Address code review feedback - improve defensive programming
Copilot Nov 23, 2025
846a0ba
Update Verify files
Nov 23, 2025
637941d
Add support for dictionary index initializers in expressions
Jan 12, 2026
08d5acf
Improve tests
Jan 13, 2026
68c5e28
Initial plan
Copilot Jan 14, 2026
49aed4a
Add C# 14 extension members support with Roslyn 5.0
Copilot Jan 14, 2026
7dcd777
Code cleanup
Jan 14, 2026
746a6a7
Merge branch 'master' into copilot/add-support-for-projectable-overloads
Jan 14, 2026
cb3105e
Add new tests for nullable types and fix GetFullTypeName
Jan 14, 2026
5f9c860
Merge pull request #146 from koenbeuk/handle-dictionary-index-initial…
PhenX Jan 14, 2026
dbc3e7b
Initial plan
Copilot Jan 21, 2026
caaf34f
Add ExpandEnumMethods property to ProjectableAttribute and implement …
Copilot Jan 21, 2026
77e4440
Prepare for better projectable enum methods interpretation
Jan 21, 2026
7ff7d26
Add ProjectableEnumMethodAttribute for compile-time enum method evalu…
Copilot Jan 21, 2026
becae46
Address code review feedback: improve comments and attribute matching
Copilot Jan 21, 2026
f42ef49
Simplify enum method expansion: call methods directly instead of inli…
Copilot Jan 22, 2026
388a3b4
Remove useless code
Jan 22, 2026
b4b2dba
Add tests for boolean, integer return types and methods with paramete…
Copilot Jan 22, 2026
e7b388e
Use SpecialType for nullable type check instead of string comparison
Copilot Jan 22, 2026
57cc931
Improve test consistency and remove obsolete Verify files
Feb 14, 2026
4b20a93
Merge branch 'master' into copilot/add-support-for-projectable-overloads
Feb 14, 2026
3ece982
Update obsolete tests
Feb 14, 2026
ede4db6
Merge pull request #143 from PhenX/copilot/add-support-for-projectabl…
PhenX Feb 14, 2026
19bf93d
Merge branch 'master' into copilot/add-csharp-14-extension-members
Feb 14, 2026
49d150c
Fix extension member method lookup after method overload support merge
Copilot Feb 14, 2026
e5d5d2e
Merge pull request #148 from PhenX/copilot/add-csharp-14-extension-me…
PhenX Feb 14, 2026
cf91085
Merge branch 'master' into copilot/expand-enum-types-properties
Feb 14, 2026
fdcecb0
Improve code readability and better handle default return values
Feb 15, 2026
a5643df
Fix phrase
Feb 15, 2026
93cb8d2
Merge pull request #150 from koenbeuk/copilot/expand-enum-types-prope…
PhenX Feb 15, 2026
0a11512
Merge branch 'master' into interface-members
Feb 28, 2026
eeb8788
Add missing verified files
Feb 28, 2026
03b1a90
Check source generator output compilation
Mar 1, 2026
72ae504
Merge pull request #156 from koenbeuk/check-source-generator-output-c…
PhenX Mar 1, 2026
5e3ce6e
Merge branch 'master' into interface-members
Mar 1, 2026
f91804d
Fix code to make generated source compilable
Mar 1, 2026
58a3177
Merge pull request #135 from rhodon-jargon/interface-members
PhenX Mar 1, 2026
97bbdbb
Merge branch 'master' into copilot/fix-getimplementingproperty-issue
Mar 1, 2026
8062bcd
Add missing verified files
Mar 1, 2026
b17732f
Apply naming suggestions
Mar 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<LangVersion>12.0</LangVersion>
<LangVersion Condition="'$(TargetFramework)' == 'net10.0'">14.0</LangVersion>
<Nullable>enable</Nullable>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<NoWarn>CS1591</NoWarn>
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
Expand Down
93 changes: 93 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,99 @@ GROUP BY (COALESCE("u"."FirstName", '') || ' ') || COALESCE("u"."LastName", '')
ORDER BY (COALESCE("u"."FirstName", '') || ' ') || COALESCE("u"."LastName", '')
```

#### How do I expand enum extension methods?
When you have an enum property and want to call an extension method on it (like getting a display name from a `[Display]` attribute), you can use the `ExpandEnumMethods` property on the `[Projectable]` attribute. This will expand the enum method call into a chain of ternary expressions for each enum value, allowing EF Core to translate it to SQL CASE expressions.

```csharp
public enum OrderStatus
{
[Display(Name = "Pending Review")]
Pending,

[Display(Name = "Approved")]
Approved,

[Display(Name = "Rejected")]
Rejected
}

public static class EnumExtensions
{
public static string GetDisplayName(this OrderStatus value)
{
// Your implementation here
return value.ToString();
}

public static bool IsApproved(this OrderStatus value)
{
return value == OrderStatus.Approved;
}

public static int GetSortOrder(this OrderStatus value)
{
return (int)value;
}

public static string Format(this OrderStatus value, string prefix)
{
return prefix + value.ToString();
}
}

public class Order
{
public int Id { get; set; }
public OrderStatus Status { get; set; }

[Projectable(ExpandEnumMethods = true)]
public string StatusName => Status.GetDisplayName();

[Projectable(ExpandEnumMethods = true)]
public bool IsStatusApproved => Status.IsApproved();

[Projectable(ExpandEnumMethods = true)]
public int StatusOrder => Status.GetSortOrder();

[Projectable(ExpandEnumMethods = true)]
public string FormattedStatus => Status.Format("Status: ");
}
```

This generates expression trees equivalent to:
```csharp
// For StatusName
@this.Status == OrderStatus.Pending ? GetDisplayName(OrderStatus.Pending)
: @this.Status == OrderStatus.Approved ? GetDisplayName(OrderStatus.Approved)
: @this.Status == OrderStatus.Rejected ? GetDisplayName(OrderStatus.Rejected)
: null

// For IsStatusApproved (boolean)
@this.Status == OrderStatus.Pending ? false
: @this.Status == OrderStatus.Approved ? true
: @this.Status == OrderStatus.Rejected ? false
: default(bool)
```

Which EF Core translates to SQL CASE expressions:
```sql
SELECT CASE
WHEN [o].[Status] = 0 THEN N'Pending Review'
WHEN [o].[Status] = 1 THEN N'Approved'
WHEN [o].[Status] = 2 THEN N'Rejected'
END AS [StatusName]
FROM [Orders] AS [o]
```

The `ExpandEnumMethods` feature supports:
- **String return types** - returns `null` as the default fallback
- **Boolean return types** - returns `default(bool)` (false) as the default fallback
- **Integer return types** - returns `default(int)` (0) as the default fallback
- **Other value types** - returns `default(T)` as the default fallback
- **Nullable enum types** - wraps the expansion in a null check
- **Methods with parameters** - parameters are passed through to each enum value call
- **Enum properties on navigation properties** - works with nested navigation

#### How does this relate to [Expressionify](https://github.com/ClaveConsulting/Expressionify)?
Expressionify is a project that was launched before this project. It has some overlapping features and uses similar approaches. When I first published this project, I was not aware of its existence, so shame on me. Currently, Expressionify targets a more focused scope of what this project is doing, and thereby it seems to be more limiting in its capabilities. Check them out though!

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EntityFrameworkCore.Projectables
namespace EntityFrameworkCore.Projectables
{
/// <summary>
/// Declares this property or method to be Projectable.
Expand All @@ -23,5 +17,26 @@ public sealed class ProjectableAttribute : Attribute
/// or null to get it from the current member.
/// </summary>
public string? UseMemberBody { get; set; }

/// <summary>
/// Get or set whether to expand enum method/extension calls by evaluating them and generating ternary
/// expressions for each enum value.
/// </summary>
/// <remarks>
/// <para>
/// When enabled, method calls on enum values are rewritten into a chain of ternary expressions that call
/// the method for each possible enum value.
/// </para>
/// <para>
/// For example, <c>MyEnumValue.GetDescription()</c> would be expanded to:
/// <c>MyEnumValue == MyEnum.Value1 ? MyEnum.Value1.GetDescription() :
/// MyEnumValue == MyEnum.Value2 ? MyEnum.Value2.GetDescription() : null</c>
/// </para>
/// <para>
/// This is useful for <c>Where()</c> and <c>OrderBy()</c> clauses where the expression
/// needs to be translated to SQL.
/// </para>
/// </remarks>
public bool ExpandEnumMethods { get; set; }
}
}
Loading