diff --git a/Directory.Build.props b/Directory.Build.props
index 726e407..876b646 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,6 +3,7 @@
net8.0;net10.0
true
12.0
+ 14.0
enable
true
CS1591
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 7f8e683..f668a8b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -24,7 +24,7 @@
-
+
diff --git a/README.md b/README.md
index 39be61e..ab688bf 100644
--- a/README.md
+++ b/README.md
@@ -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!
diff --git a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs
index 4c3082b..30af683 100644
--- a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs
+++ b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs
@@ -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
{
///
/// Declares this property or method to be Projectable.
@@ -23,5 +17,26 @@ public sealed class ProjectableAttribute : Attribute
/// or null to get it from the current member.
///
public string? UseMemberBody { get; set; }
+
+ ///
+ /// Get or set whether to expand enum method/extension calls by evaluating them and generating ternary
+ /// expressions for each enum value.
+ ///
+ ///
+ ///
+ /// When enabled, method calls on enum values are rewritten into a chain of ternary expressions that call
+ /// the method for each possible enum value.
+ ///
+ ///
+ /// For example, MyEnumValue.GetDescription() would be expanded to:
+ /// MyEnumValue == MyEnum.Value1 ? MyEnum.Value1.GetDescription() :
+ /// MyEnumValue == MyEnum.Value2 ? MyEnum.Value2.GetDescription() : null
+ ///
+ ///
+ /// This is useful for Where() and OrderBy() clauses where the expression
+ /// needs to be translated to SQL.
+ ///
+ ///
+ public bool ExpandEnumMethods { get; set; }
}
}
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
index 6340d90..f51c8f6 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ExpressionSyntaxRewriter.cs
@@ -2,11 +2,6 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace EntityFrameworkCore.Projectables.Generator
{
@@ -16,15 +11,19 @@ public class ExpressionSyntaxRewriter : CSharpSyntaxRewriter
readonly INamedTypeSymbol _targetTypeSymbol;
readonly SemanticModel _semanticModel;
readonly NullConditionalRewriteSupport _nullConditionalRewriteSupport;
+ readonly bool _expandEnumMethods;
readonly SourceProductionContext _context;
readonly Stack _conditionalAccessExpressionsStack = new();
-
- public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullConditionalRewriteSupport nullConditionalRewriteSupport, SemanticModel semanticModel, SourceProductionContext context)
+ readonly string? _extensionParameterName;
+
+ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullConditionalRewriteSupport nullConditionalRewriteSupport, bool expandEnumMethods, SemanticModel semanticModel, SourceProductionContext context, string? extensionParameterName = null)
{
_targetTypeSymbol = targetTypeSymbol;
_nullConditionalRewriteSupport = nullConditionalRewriteSupport;
+ _expandEnumMethods = expandEnumMethods;
_semanticModel = semanticModel;
_context = context;
+ _extensionParameterName = extensionParameterName;
}
private SyntaxNode? VisitThisBaseExpression(CSharpSyntaxNode node)
@@ -53,30 +52,191 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
public override SyntaxNode? VisitInvocationExpression(InvocationExpressionSyntax node)
{
// Fully qualify extension method calls
- if (node.Expression is MemberAccessExpressionSyntax memberAccessExpressionSyntax)
+ if (node.Expression is not MemberAccessExpressionSyntax memberAccessExpressionSyntax)
{
- var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
- if (symbol is IMethodSymbol { IsExtensionMethod: true } methodSymbol)
- {
- return SyntaxFactory.InvocationExpression(
- SyntaxFactory.MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- SyntaxFactory.ParseName(methodSymbol.ContainingType.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat)),
- memberAccessExpressionSyntax.Name
- ),
- node.ArgumentList.WithArguments(
- ((ArgumentListSyntax)VisitArgumentList(node.ArgumentList)!).Arguments.Insert(0, SyntaxFactory.Argument(
- (ExpressionSyntax)Visit(memberAccessExpressionSyntax.Expression)
- )
+ return base.VisitInvocationExpression(node);
+ }
+
+ var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
+ if (symbol is not IMethodSymbol methodSymbol)
+ {
+ return base.VisitInvocationExpression(node);
+ }
+
+ // Check if we should expand enum methods
+ if (_expandEnumMethods && TryExpandEnumMethodCall(node, memberAccessExpressionSyntax, methodSymbol, out var expandedExpression))
+ {
+ return expandedExpression;
+ }
+
+ // Fully qualify extension method calls
+ if (methodSymbol.IsExtensionMethod)
+ {
+ return SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.ParseName(methodSymbol.ContainingType.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat)),
+ memberAccessExpressionSyntax.Name
+ ),
+ node.ArgumentList.WithArguments(
+ ((ArgumentListSyntax)VisitArgumentList(node.ArgumentList)!).Arguments.Insert(0, SyntaxFactory.Argument(
+ (ExpressionSyntax)Visit(memberAccessExpressionSyntax.Expression)
)
)
- );
- }
+ )
+ );
}
return base.VisitInvocationExpression(node);
}
+ private bool TryExpandEnumMethodCall(InvocationExpressionSyntax node, MemberAccessExpressionSyntax memberAccess, IMethodSymbol methodSymbol, out ExpressionSyntax? expandedExpression)
+ {
+ expandedExpression = null;
+
+ // Get the receiver expression (the enum instance or variable)
+ var receiverExpression = memberAccess.Expression;
+ var receiverTypeInfo = _semanticModel.GetTypeInfo(receiverExpression);
+ var receiverType = receiverTypeInfo.Type;
+
+ // Handle nullable enum types
+ ITypeSymbol enumType;
+ var isNullable = false;
+ if (receiverType is INamedTypeSymbol { IsGenericType: true, Name: "Nullable" } nullableType &&
+ nullableType.TypeArguments.Length == 1 &&
+ nullableType.TypeArguments[0].TypeKind == TypeKind.Enum)
+ {
+ enumType = nullableType.TypeArguments[0];
+ isNullable = true;
+ }
+ else if (receiverType?.TypeKind == TypeKind.Enum)
+ {
+ enumType = receiverType;
+ }
+ else
+ {
+ // Not an enum type
+ return false;
+ }
+
+ // Get all enum members
+ var enumMembers = enumType.GetMembers()
+ .OfType()
+ .Where(f => f.HasConstantValue)
+ .ToList();
+
+ if (enumMembers.Count == 0)
+ {
+ return false;
+ }
+
+ // Visit the receiver expression to transform it (e.g., @this.MyProperty)
+ var visitedReceiver = (ExpressionSyntax)Visit(receiverExpression);
+
+ // Get the original method (in case of reduced extension method)
+ var originalMethod = methodSymbol.ReducedFrom ?? methodSymbol;
+
+ // Get the return type of the method to determine the default value
+ var returnType = methodSymbol.ReturnType;
+
+ // Build a chain of ternary expressions for each enum value
+ // Start with default(T) as the fallback for non-nullable types, or null for nullable/reference types
+ ExpressionSyntax defaultExpression;
+ if (returnType.IsReferenceType || returnType.NullableAnnotation == NullableAnnotation.Annotated ||
+ returnType is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T })
+ {
+ defaultExpression = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression);
+ }
+ else
+ {
+ // Use default(T) for value types
+ defaultExpression = SyntaxFactory.DefaultExpression(
+ SyntaxFactory.ParseTypeName(returnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)));
+ }
+
+ var currentExpression = defaultExpression;
+
+ // Create the enum value access: EnumType.Value
+ var enumAccessValues = enumMembers
+ .AsEnumerable()
+ .Reverse()
+ .Select(m =>
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.ParseTypeName(enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)),
+ SyntaxFactory.IdentifierName(m.Name)
+ )
+ );
+
+ // Build the ternary chain, calling the method on each enum value
+ foreach (var enumValueAccess in enumAccessValues)
+ {
+ // Create the method call on the enum value: ExtensionClass.Method(EnumType.Value)
+ var methodCall = CreateMethodCallOnEnumValue(originalMethod, enumValueAccess, node.ArgumentList);
+
+ // Create condition: receiver == EnumType.Value
+ var condition = SyntaxFactory.BinaryExpression(
+ SyntaxKind.EqualsExpression,
+ visitedReceiver,
+ enumValueAccess
+ );
+
+ // Create conditional expression: condition ? methodCall : previousExpression
+ currentExpression = SyntaxFactory.ConditionalExpression(
+ condition,
+ methodCall,
+ currentExpression
+ );
+ }
+
+ // If nullable, wrap in null check
+ if (isNullable)
+ {
+ var nullCheck = SyntaxFactory.BinaryExpression(
+ SyntaxKind.EqualsExpression,
+ visitedReceiver,
+ SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)
+ );
+
+ currentExpression = SyntaxFactory.ConditionalExpression(
+ nullCheck,
+ defaultExpression,
+ currentExpression
+ );
+ }
+
+ expandedExpression = SyntaxFactory.ParenthesizedExpression(currentExpression);
+ return true;
+ }
+
+ private ExpressionSyntax CreateMethodCallOnEnumValue(IMethodSymbol methodSymbol, ExpressionSyntax enumValueExpression, ArgumentListSyntax originalArguments)
+ {
+ // Get the fully qualified containing type name
+ var containingTypeName = methodSymbol.ContainingType.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat);
+
+ // Create the method access expression: ContainingType.MethodName
+ var methodAccess = SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.ParseName(containingTypeName),
+ SyntaxFactory.IdentifierName(methodSymbol.Name)
+ );
+
+ // Build arguments: the enum value as the first argument (for extension methods), followed by any additional arguments
+ var arguments = SyntaxFactory.SeparatedList();
+ arguments = arguments.Add(SyntaxFactory.Argument(enumValueExpression));
+
+ // Add any additional arguments from the original call
+ foreach (var arg in originalArguments.Arguments)
+ {
+ arguments = arguments.Add((ArgumentSyntax)Visit(arg));
+ }
+
+ return SyntaxFactory.InvocationExpression(
+ methodAccess,
+ SyntaxFactory.ArgumentList(arguments)
+ );
+ }
+
public override SyntaxNode? VisitInterpolation(InterpolationSyntax node)
{
// Visit the expression first
@@ -281,8 +441,22 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
{
- var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
- if (symbol is not null)
+ // Handle C# 14 extension parameter replacement (e.g., `e` in `extension(Entity e)` becomes `@this`)
+ if (_extensionParameterName is not null && node.Identifier.Text == _extensionParameterName)
+ {
+ var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
+
+ // Check if this identifier refers to the extension parameter
+ if (symbol is IParameterSymbol { ContainingSymbol: INamedTypeSymbol { IsExtension: true } })
+ {
+ return SyntaxFactory.IdentifierName("@this")
+ .WithLeadingTrivia(node.GetLeadingTrivia())
+ .WithTrailingTrivia(node.GetTrailingTrivia());
+ }
+ }
+
+ var identifierSymbol = _semanticModel.GetSymbolInfo(node).Symbol;
+ if (identifierSymbol is not null)
{
var operation = node switch { { Parent: { } parent } when parent.IsKind(SyntaxKind.InvocationExpression) => _semanticModel.GetOperation(node.Parent),
_ => _semanticModel.GetOperation(node!)
@@ -337,10 +511,10 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
}
// if this node refers to a named type which is not yet fully qualified, we want to fully qualify it
- if (symbol.Kind is SymbolKind.NamedType && node.Parent?.Kind() is not SyntaxKind.QualifiedName)
+ if (identifierSymbol.Kind is SymbolKind.NamedType && node.Parent?.Kind() is not SyntaxKind.QualifiedName)
{
return SyntaxFactory.ParseTypeName(
- symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
+ identifierSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
).WithLeadingTrivia(node.GetLeadingTrivia());
}
}
@@ -385,7 +559,63 @@ public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, NullCondition
return base.VisitNullableType(node);
}
-
+
+ public override SyntaxNode? VisitInitializerExpression(InitializerExpressionSyntax node)
+ {
+ // Only handle object initializers that might contain indexer assignments
+ if (!node.IsKind(SyntaxKind.ObjectInitializerExpression))
+ {
+ return base.VisitInitializerExpression(node);
+ }
+
+ // Check if any expression is an indexer assignment (e.g., ["key"] = value)
+ var hasIndexerAssignment = node.Expressions.Any(e =>
+ e is AssignmentExpressionSyntax { Left: ImplicitElementAccessSyntax });
+
+ if (!hasIndexerAssignment)
+ {
+ return base.VisitInitializerExpression(node);
+ }
+
+ var newExpressions = new SeparatedSyntaxList();
+
+ foreach (var expression in node.Expressions)
+ {
+ if (expression is AssignmentExpressionSyntax assignment &&
+ assignment.Left is ImplicitElementAccessSyntax implicitElementAccess)
+ {
+ // Transform ["key"] = value into { "key", value }
+ var arguments = new SeparatedSyntaxList();
+
+ foreach (var argument in implicitElementAccess.ArgumentList.Arguments)
+ {
+ var visitedArgument = (ExpressionSyntax?)Visit(argument.Expression) ?? argument.Expression;
+ arguments = arguments.Add(visitedArgument);
+ }
+
+ var visitedValue = (ExpressionSyntax?)Visit(assignment.Right) ?? assignment.Right;
+ arguments = arguments.Add(visitedValue);
+
+ var complexElementInitializer = SyntaxFactory.InitializerExpression(
+ SyntaxKind.ComplexElementInitializerExpression,
+ arguments
+ );
+
+ newExpressions = newExpressions.Add(complexElementInitializer);
+ }
+ else
+ {
+ var visitedExpression = (ExpressionSyntax?)Visit(expression) ?? expression;
+ newExpressions = newExpressions.Add(visitedExpression);
+ }
+ }
+
+ return SyntaxFactory.InitializerExpression(
+ SyntaxKind.CollectionInitializerExpression,
+ newExpressions
+ ).WithTriviaFrom(node);
+ }
+
private ExpressionSyntax ReplaceVariableWithCast(ExpressionSyntax expression, DeclarationPatternSyntax declaration, ExpressionSyntax governingExpression)
{
if (declaration.Designation is SingleVariableDesignationSyntax variableDesignation)
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableDescriptor.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableDescriptor.cs
index f428a76..291cb5b 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableDescriptor.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableDescriptor.cs
@@ -33,6 +33,8 @@ public class ProjectableDescriptor
public ParameterListSyntax? ParametersList { get; set; }
+ public IEnumerable? ParameterTypeNames { get; set; }
+
public TypeParameterListSyntax? TypeParameterList { get; set; }
public SyntaxList? ConstraintClauses { get; set; }
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
index 339b46b..8a84310 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs
@@ -22,6 +22,24 @@ static IEnumerable GetNestedInClassPath(ITypeSymbol namedTypeSymbol)
yield return namedTypeSymbol.Name;
}
+ ///
+ /// Gets the nested class path for extension members, skipping the extension block itself
+ /// and using the outer class as the containing type.
+ ///
+ static IEnumerable GetNestedInClassPathForExtensionMember(ITypeSymbol extensionType)
+ {
+ // For extension members, the ContainingType is the extension block,
+ // and its ContainingType is the outer class (e.g., EntityExtensions)
+ var outerType = extensionType.ContainingType;
+
+ if (outerType is not null)
+ {
+ return GetNestedInClassPath(outerType);
+ }
+
+ return [];
+ }
+
public static ProjectableDescriptor? GetDescriptor(Compilation compilation, MemberDeclarationSyntax member, SourceProductionContext context)
{
var semanticModel = compilation.GetSemanticModel(member.SyntaxTree);
@@ -57,6 +75,11 @@ static IEnumerable GetNestedInClassPath(ITypeSymbol namedTypeSymbol)
.OfType()
.FirstOrDefault();
+ var expandEnumMethods = projectableAttributeClass.NamedArguments
+ .Where(x => x.Key == "ExpandEnumMethods")
+ .Select(x => x.Value.Value is bool b && b)
+ .FirstOrDefault();
+
var memberBody = member;
if (useMemberBody is not null)
@@ -115,24 +138,73 @@ x is IPropertySymbol xProperty &&
if (memberBody is null) return null;
}
- var expressionSyntaxRewriter = new ExpressionSyntaxRewriter(memberSymbol.ContainingType, nullConditionalRewriteSupport, semanticModel, context);
+ // Check if this member is inside a C# 14 extension block
+ var isExtensionMember = memberSymbol.ContainingType is { IsExtension: true };
+ IParameterSymbol? extensionParameter = null;
+ ITypeSymbol? extensionReceiverType = null;
+
+ if (isExtensionMember && memberSymbol.ContainingType is { } extensionType)
+ {
+ extensionParameter = extensionType.ExtensionParameter;
+ extensionReceiverType = extensionParameter?.Type;
+ }
+
+ // For extension members, use the extension receiver type for rewriting
+ var targetTypeForRewriting = isExtensionMember && extensionReceiverType is INamedTypeSymbol receiverNamedType
+ ? receiverNamedType
+ : memberSymbol.ContainingType;
+
+ var expressionSyntaxRewriter = new ExpressionSyntaxRewriter(
+ targetTypeForRewriting,
+ nullConditionalRewriteSupport,
+ expandEnumMethods,
+ semanticModel,
+ context,
+ extensionParameter?.Name);
var declarationSyntaxRewriter = new DeclarationSyntaxRewriter(semanticModel);
- var descriptor = new ProjectableDescriptor {
+ // For extension members, use the outer class for class naming
+ var classForNaming = isExtensionMember && memberSymbol.ContainingType.ContainingType is not null
+ ? memberSymbol.ContainingType.ContainingType
+ : memberSymbol.ContainingType;
+ var descriptor = new ProjectableDescriptor
+ {
UsingDirectives = member.SyntaxTree.GetRoot().DescendantNodes().OfType(),
- ClassName = memberSymbol.ContainingType.Name,
- ClassNamespace = memberSymbol.ContainingType.ContainingNamespace.IsGlobalNamespace ? null : memberSymbol.ContainingType.ContainingNamespace.ToDisplayString(),
+ ClassName = classForNaming.Name,
+ ClassNamespace = classForNaming.ContainingNamespace.IsGlobalNamespace ? null : classForNaming.ContainingNamespace.ToDisplayString(),
MemberName = memberSymbol.Name,
- NestedInClassNames = GetNestedInClassPath(memberSymbol.ContainingType),
+ NestedInClassNames = isExtensionMember
+ ? GetNestedInClassPathForExtensionMember(memberSymbol.ContainingType)
+ : GetNestedInClassPath(memberSymbol.ContainingType),
ParametersList = SyntaxFactory.ParameterList()
};
+
+ var methodSymbol = memberSymbol as IMethodSymbol;
+
+ // Collect parameter type names for method overload disambiguation
+ if (methodSymbol is not null)
+ {
+ var parameterTypeNames = methodSymbol.Parameters
+ .Select(p => p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
+ .ToList();
+
+ // For extension members, prepend the extension receiver type to match how the runtime sees the method.
+ // At runtime, extension member methods have the receiver as the first parameter, but Roslyn's
+ // methodSymbol.Parameters doesn't include it.
+ if (isExtensionMember && extensionReceiverType is not null)
+ {
+ parameterTypeNames.Insert(0, extensionReceiverType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+ }
+
+ descriptor.ParameterTypeNames = parameterTypeNames;
+ }
- if (memberSymbol.ContainingType is INamedTypeSymbol { IsGenericType: true } containingNamedType)
+ if (classForNaming is { IsGenericType: true })
{
descriptor.ClassTypeParameterList = SyntaxFactory.TypeParameterList();
- foreach (var additionalClassTypeParameter in containingNamedType.TypeParameters)
+ foreach (var additionalClassTypeParameter in classForNaming.TypeParameters)
{
descriptor.ClassTypeParameterList = descriptor.ClassTypeParameterList.AddParameters(
SyntaxFactory.TypeParameter(additionalClassTypeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
@@ -182,7 +254,21 @@ x is IPropertySymbol xProperty &&
}
}
- if (!member.Modifiers.Any(SyntaxKind.StaticKeyword))
+ // Handle extension members - add @this parameter with the extension receiver type
+ if (isExtensionMember && extensionReceiverType is not null)
+ {
+ descriptor.ParametersList = descriptor.ParametersList.AddParameters(
+ SyntaxFactory.Parameter(
+ SyntaxFactory.Identifier("@this")
+ )
+ .WithType(
+ SyntaxFactory.ParseTypeName(
+ extensionReceiverType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
+ )
+ )
+ );
+ }
+ else if (!member.Modifiers.Any(SyntaxKind.StaticKeyword))
{
descriptor.ParametersList = descriptor.ParametersList.AddParameters(
SyntaxFactory.Parameter(
@@ -196,9 +282,13 @@ x is IPropertySymbol xProperty &&
);
}
- var methodSymbol = memberSymbol as IMethodSymbol;
-
- if (methodSymbol is { IsExtensionMethod: true })
+ // Handle target type for extension members
+ if (isExtensionMember && extensionReceiverType is not null)
+ {
+ descriptor.TargetClassNamespace = extensionReceiverType.ContainingNamespace.IsGlobalNamespace ? null : extensionReceiverType.ContainingNamespace.ToDisplayString();
+ descriptor.TargetNestedInClassNames = GetNestedInClassPath(extensionReceiverType);
+ }
+ else if (methodSymbol is { IsExtensionMethod: true })
{
var targetTypeSymbol = methodSymbol.Parameters.First().Type;
descriptor.TargetClassNamespace = targetTypeSymbol.ContainingNamespace.IsGlobalNamespace ? null : targetTypeSymbol.ContainingNamespace.ToDisplayString();
diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs
index 6f177bc..abc89bd 100644
--- a/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs
+++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs
@@ -63,7 +63,7 @@ static void Execute(MemberDeclarationSyntax member, Compilation compilation, Sou
throw new InvalidOperationException("Expected a memberName here");
}
- var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName);
+ var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName, projectable.ParameterTypeNames);
var generatedFileName = projectable.ClassTypeParameterList is not null ? $"{generatedClassName}-{projectable.ClassTypeParameterList.ChildNodes().Count()}.g.cs" : $"{generatedClassName}.g.cs";
var classSyntax = ClassDeclaration(generatedClassName)
diff --git a/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs b/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs
index bc86a95..7905218 100644
--- a/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs
+++ b/src/EntityFrameworkCore.Projectables/Extensions/TypeExtensions.cs
@@ -1,12 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Reflection.Metadata;
-using System.Runtime.CompilerServices;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection;
namespace EntityFrameworkCore.Projectables.Extensions
{
@@ -137,13 +129,32 @@ public static PropertyInfo GetImplementingProperty(this Type derivedType, Proper
return propertyInfo;
}
- var derivedProperties = derivedType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ var implementingType = implementingAccessor.DeclaringType
+ // This should only be null if it is a property accessor on the global module,
+ // which should never happen since we found it from derivedType
+ ?? throw new ApplicationException("The property accessor has no declaring type!");
+
+ var derivedProperties = implementingType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- return derivedProperties.First(propertyInfo.GetMethod == accessor
- ? p => p.GetMethod == implementingAccessor
- : p => p.SetMethod == implementingAccessor);
+ return derivedProperties.FirstOrDefault(propertyInfo.GetMethod == accessor
+ ? p => MethodInfosEqual(p.GetMethod, implementingAccessor)
+ : p => MethodInfosEqual(p.SetMethod, implementingAccessor)) ?? propertyInfo;
}
+ ///
+ /// The built-in
+ /// does not work if the s don't agree.
+ ///
+ private static bool MethodInfosEqual(MethodInfo? first, MethodInfo second)
+ => first?.ReflectedType == second.ReflectedType
+ ? first == second
+ : first is not null
+ && first.DeclaringType == second.DeclaringType
+ && first.Name == second.Name
+ && first.GetParameters().Select(p => p.ParameterType)
+ .SequenceEqual(second.GetParameters().Select(p => p.ParameterType))
+ && first.GetGenericArguments().SequenceEqual(second.GetGenericArguments());
+
public static MethodInfo GetConcreteMethod(this Type derivedType, MethodInfo methodInfo)
=> methodInfo.DeclaringType?.IsInterface == true
? derivedType.GetImplementingMethod(methodInfo)
diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
index 1c63e77..d21c8ce 100644
--- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
+++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionClassNameGenerator.cs
@@ -11,21 +11,31 @@ public static class ProjectionExpressionClassNameGenerator
public const string Namespace = "EntityFrameworkCore.Projectables.Generated";
public static string GenerateName(string? namespaceName, IEnumerable? nestedInClassNames, string memberName)
+ {
+ return GenerateName(namespaceName, nestedInClassNames, memberName, null);
+ }
+
+ public static string GenerateName(string? namespaceName, IEnumerable? nestedInClassNames, string memberName, IEnumerable? parameterTypeNames)
{
var stringBuilder = new StringBuilder();
- return GenerateNameImpl(stringBuilder, namespaceName, nestedInClassNames, memberName);
+ return GenerateNameImpl(stringBuilder, namespaceName, nestedInClassNames, memberName, parameterTypeNames);
}
public static string GenerateFullName(string? namespaceName, IEnumerable? nestedInClassNames, string memberName)
+ {
+ return GenerateFullName(namespaceName, nestedInClassNames, memberName, null);
+ }
+
+ public static string GenerateFullName(string? namespaceName, IEnumerable? nestedInClassNames, string memberName, IEnumerable? parameterTypeNames)
{
var stringBuilder = new StringBuilder(Namespace);
stringBuilder.Append('.');
- return GenerateNameImpl(stringBuilder, namespaceName, nestedInClassNames, memberName);
+ return GenerateNameImpl(stringBuilder, namespaceName, nestedInClassNames, memberName, parameterTypeNames);
}
- static string GenerateNameImpl(StringBuilder stringBuilder, string? namespaceName, IEnumerable? nestedInClassNames, string memberName)
+ static string GenerateNameImpl(StringBuilder stringBuilder, string? namespaceName, IEnumerable? nestedInClassNames, string memberName, IEnumerable? parameterTypeNames)
{
stringBuilder.Append(namespaceName?.Replace('.', '_'));
stringBuilder.Append('_');
@@ -55,8 +65,37 @@ static string GenerateNameImpl(StringBuilder stringBuilder, string? namespaceNam
}
}
- stringBuilder.Append(memberName);
+ stringBuilder.Append(memberName.Replace(".", "__")); // Support explicit interface implementations
+
+ // Add parameter types to make method overloads unique
+ if (parameterTypeNames is not null)
+ {
+ var parameterIndex = 0;
+ foreach (var parameterTypeName in parameterTypeNames)
+ {
+ stringBuilder.Append("_P");
+ stringBuilder.Append(parameterIndex);
+ stringBuilder.Append('_');
+ // Replace characters that are not valid in type names with underscores
+ var sanitizedTypeName = parameterTypeName
+ .Replace("global::", "") // Remove global:: prefix
+ .Replace('.', '_')
+ .Replace('<', '_')
+ .Replace('>', '_')
+ .Replace(',', '_')
+ .Replace(' ', '_')
+ .Replace('[', '_')
+ .Replace(']', '_')
+ .Replace('`', '_')
+ .Replace(':', '_') // Additional safety for any remaining colons
+ .Replace('?', '_'); // Handle nullable reference types
+ stringBuilder.Append(sanitizedTypeName);
+ parameterIndex++;
+ }
+ }
+ // Add generic arity at the very end (after parameter types)
+ // This matches how the CLR names generic types
if (arity > 0)
{
stringBuilder.Append('`');
diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs
index b9062dd..b6ded59 100644
--- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs
+++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs
@@ -62,7 +62,33 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo
static LambdaExpression? GetExpressionFromGeneratedType(MemberInfo projectableMemberInfo)
{
var declaringType = projectableMemberInfo.DeclaringType ?? throw new InvalidOperationException("Expected a valid type here");
- var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name);
+
+ // Keep track of the original declaring type's generic arguments for later use
+ var originalDeclaringType = declaringType;
+
+ // For generic types, use the generic type definition to match the generated name
+ // which is based on the open generic type
+ if (declaringType.IsGenericType && !declaringType.IsGenericTypeDefinition)
+ {
+ declaringType = declaringType.GetGenericTypeDefinition();
+ }
+
+ // Get parameter types for method overload disambiguation
+ // Use the same format as Roslyn's SymbolDisplayFormat.FullyQualifiedFormat
+ // which uses C# keywords for primitive types (int, string, etc.)
+ string[]? parameterTypeNames = null;
+ if (projectableMemberInfo is MethodInfo method)
+ {
+ // For generic methods, use the generic definition to get parameter types
+ // This ensures type parameters like TEntity are used instead of concrete types
+ var methodToInspect = method.IsGenericMethod ? method.GetGenericMethodDefinition() : method;
+
+ parameterTypeNames = methodToInspect.GetParameters()
+ .Select(p => GetFullTypeName(p.ParameterType))
+ .ToArray();
+ }
+
+ var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name, parameterTypeNames);
var expressionFactoryType = declaringType.Assembly.GetType(generatedContainingTypeName);
@@ -70,7 +96,7 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo
{
if (expressionFactoryType.IsGenericTypeDefinition)
{
- expressionFactoryType = expressionFactoryType.MakeGenericType(declaringType.GenericTypeArguments);
+ expressionFactoryType = expressionFactoryType.MakeGenericType(originalDeclaringType.GenericTypeArguments);
}
var expressionFactoryMethod = expressionFactoryType.GetMethod("Expression", BindingFlags.Static | BindingFlags.NonPublic);
@@ -93,6 +119,99 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo
return null;
}
+
+ static string GetFullTypeName(Type type)
+ {
+ // Handle generic type parameters (e.g., T, TEntity)
+ if (type.IsGenericParameter)
+ {
+ return type.Name;
+ }
+
+ // Handle nullable value types (e.g., int? -> int?)
+ var underlyingType = Nullable.GetUnderlyingType(type);
+ if (underlyingType != null)
+ {
+ return $"{GetFullTypeName(underlyingType)}?";
+ }
+
+ // Handle array types
+ if (type.IsArray)
+ {
+ var elementType = type.GetElementType();
+ if (elementType == null)
+ {
+ // Fallback for edge cases where GetElementType() might return null
+ return type.Name;
+ }
+
+ var rank = type.GetArrayRank();
+ var elementTypeName = GetFullTypeName(elementType);
+
+ if (rank == 1)
+ {
+ return $"{elementTypeName}[]";
+ }
+ else
+ {
+ var commas = new string(',', rank - 1);
+ return $"{elementTypeName}[{commas}]";
+ }
+ }
+
+ // Map primitive types to their C# keyword equivalents to match Roslyn's output
+ var typeKeyword = GetCSharpKeyword(type);
+ if (typeKeyword != null)
+ {
+ return typeKeyword;
+ }
+
+ // For generic types, construct the full name matching Roslyn's format
+ if (type.IsGenericType)
+ {
+ var genericTypeDef = type.GetGenericTypeDefinition();
+ var genericArgs = type.GetGenericArguments();
+ var baseName = genericTypeDef.FullName ?? genericTypeDef.Name;
+
+ // Remove the `n suffix (e.g., `1, `2)
+ var backtickIndex = baseName.IndexOf('`');
+ if (backtickIndex > 0)
+ {
+ baseName = baseName.Substring(0, backtickIndex);
+ }
+
+ var args = string.Join(", ", genericArgs.Select(GetFullTypeName));
+ return $"{baseName}<{args}>";
+ }
+
+ if (type.FullName != null)
+ {
+ // Replace + with . for nested types to match Roslyn's format
+ return type.FullName.Replace('+', '.');
+ }
+
+ return type.Name;
+ }
+
+ static string? GetCSharpKeyword(Type type)
+ {
+ if (type == typeof(bool)) return "bool";
+ if (type == typeof(byte)) return "byte";
+ if (type == typeof(sbyte)) return "sbyte";
+ if (type == typeof(char)) return "char";
+ if (type == typeof(decimal)) return "decimal";
+ if (type == typeof(double)) return "double";
+ if (type == typeof(float)) return "float";
+ if (type == typeof(int)) return "int";
+ if (type == typeof(uint)) return "uint";
+ if (type == typeof(long)) return "long";
+ if (type == typeof(ulong)) return "ulong";
+ if (type == typeof(short)) return "short";
+ if (type == typeof(ushort)) return "ushort";
+ if (type == typeof(object)) return "object";
+ if (type == typeof(string)) return "string";
+ return null;
+ }
}
}
}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.DotNet10_0.verified.txt
new file mode 100644
index 0000000..7519fa3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.DotNet10_0.verified.txt
@@ -0,0 +1,3 @@
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE 1 > [t].[Id] AND NEWID() <> '00000000-0000-0000-0000-000000000000'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.DotNet9_0.verified.txt
new file mode 100644
index 0000000..7519fa3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.DotNet9_0.verified.txt
@@ -0,0 +1,3 @@
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE 1 > [t].[Id] AND NEWID() <> '00000000-0000-0000-0000-000000000000'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.verified.txt
new file mode 100644
index 0000000..7519fa3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.NullableValueTypes.verified.txt
@@ -0,0 +1,3 @@
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE 1 > [t].[Id] AND NEWID() <> '00000000-0000-0000-0000-000000000000'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.cs
index 843220c..a3028c6 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexArgumentsTests.cs
@@ -25,6 +25,9 @@ public class TestEntity
[Projectable]
public bool IsValid3(params int[] validIds) => validIds.Contains(Id);
+
+ [Projectable]
+ public bool IsValid4(int? validId, Guid? guid) => validId > Id && guid != Guid.Parse("00000000-0000-0000-0000-000000000000");
}
[Fact]
@@ -52,8 +55,7 @@ public Task ArrayOfPrimitivesArguments()
return Verifier.Verify(query.ToQueryString());
}
-
-
+
[Fact]
public Task ParamsOfPrimitivesArguments()
{
@@ -64,5 +66,16 @@ public Task ParamsOfPrimitivesArguments()
return Verifier.Verify(query.ToQueryString());
}
+
+ [Fact]
+ public Task NullableValueTypes()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Where(x => x.IsValid4(1, Guid.NewGuid()));
+
+ return Verifier.Verify(query.ToQueryString());
+ }
}
}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.DotNet10_0.verified.txt
new file mode 100644
index 0000000..d544207
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [g].[Id]
+FROM [GenericObject] AS [g]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.DotNet9_0.verified.txt
new file mode 100644
index 0000000..d544207
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [g].[Id]
+FROM [GenericObject] AS [g]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.verified.txt
new file mode 100644
index 0000000..d544207
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.ProjectOverGenericType.verified.txt
@@ -0,0 +1,2 @@
+SELECT [g].[Id]
+FROM [GenericObject] AS [g]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs
index a87da15..4de2fb9 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ComplexModelTests.cs
@@ -52,6 +52,11 @@ public class Order
public DateTime RecordDate { get; set; }
}
+
+ public class GenericObject
+ {
+ public T Id { get; set; }
+ }
[Fact]
public Task ProjectOverNavigationProperty()
@@ -98,5 +103,16 @@ public Task ProjectQueryFilters()
return Verifier.Verify(query.ToQueryString());
}
+
+ [Fact]
+ public Task ProjectOverGenericType()
+ {
+ using var dbContext = new SampleDbContext>();
+
+ var query = dbContext.Set>()
+ .Select(x => x.Id);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
}
}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj b/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj
index 81385dc..cd9ad0e 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj
@@ -22,11 +22,6 @@
-
-
-
-
-
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.DotNet10_0.verified.txt
new file mode 100644
index 0000000..df4ace9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.DotNet10_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+WHERE CASE
+ WHEN [o].[Status] = 0 THEN CAST(0 AS bit)
+ WHEN [o].[Status] = 1 THEN CAST(1 AS bit)
+ WHEN [o].[Status] = 2 THEN CAST(0 AS bit)
+ ELSE CAST(0 AS bit)
+END = CAST(1 AS bit)
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.DotNet9_0.verified.txt
new file mode 100644
index 0000000..df4ace9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.DotNet9_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+WHERE CASE
+ WHEN [o].[Status] = 0 THEN CAST(0 AS bit)
+ WHEN [o].[Status] = 1 THEN CAST(1 AS bit)
+ WHEN [o].[Status] = 2 THEN CAST(0 AS bit)
+ ELSE CAST(0 AS bit)
+END = CAST(1 AS bit)
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.verified.txt
new file mode 100644
index 0000000..df4ace9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnBooleanEnumExpansion.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+WHERE CASE
+ WHEN [o].[Status] = 0 THEN CAST(0 AS bit)
+ WHEN [o].[Status] = 1 THEN CAST(1 AS bit)
+ WHEN [o].[Status] = 2 THEN CAST(0 AS bit)
+ ELSE CAST(0 AS bit)
+END = CAST(1 AS bit)
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..51cd671
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+WHERE CASE
+ WHEN [o].[Status] = 0 THEN N'Pending Review'
+ WHEN [o].[Status] = 1 THEN N'Approved'
+ WHEN [o].[Status] = 2 THEN N'Rejected'
+END = N'Pending Review'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..51cd671
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+WHERE CASE
+ WHEN [o].[Status] = 0 THEN N'Pending Review'
+ WHEN [o].[Status] = 1 THEN N'Approved'
+ WHEN [o].[Status] = 2 THEN N'Rejected'
+END = N'Pending Review'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.verified.txt
new file mode 100644
index 0000000..f3ab7c0
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.FilterOnExpandedEnumProperty.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+WHERE CASE
+ WHEN [o].[Status] = 0 THEN N'Pending Review'
+ WHEN [o].[Status] = 1 THEN N'Approved'
+ WHEN [o].[Status] = 2 THEN N'Rejected'
+ ELSE NULL
+END = N'Pending Review'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..43bbda6
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+ORDER BY CASE
+ WHEN [o].[Status] = 0 THEN N'Pending Review'
+ WHEN [o].[Status] = 1 THEN N'Approved'
+ WHEN [o].[Status] = 2 THEN N'Rejected'
+END
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..43bbda6
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+ORDER BY CASE
+ WHEN [o].[Status] = 0 THEN N'Pending Review'
+ WHEN [o].[Status] = 1 THEN N'Approved'
+ WHEN [o].[Status] = 2 THEN N'Rejected'
+END
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.verified.txt
new file mode 100644
index 0000000..2945827
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByExpandedEnumProperty.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+ORDER BY CASE
+ WHEN [o].[Status] = 0 THEN N'Pending Review'
+ WHEN [o].[Status] = 1 THEN N'Approved'
+ WHEN [o].[Status] = 2 THEN N'Rejected'
+ ELSE NULL
+END
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.DotNet10_0.verified.txt
new file mode 100644
index 0000000..55bbdc9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.DotNet10_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+ORDER BY CASE
+ WHEN COALESCE([o].[Priority], 0) = 0 THEN 1
+ WHEN COALESCE([o].[Priority], 0) = 1 THEN 2
+ WHEN COALESCE([o].[Priority], 0) = 2 THEN 3
+ ELSE 0
+END
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.DotNet9_0.verified.txt
new file mode 100644
index 0000000..55bbdc9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.DotNet9_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+ORDER BY CASE
+ WHEN COALESCE([o].[Priority], 0) = 0 THEN 1
+ WHEN COALESCE([o].[Priority], 0) = 1 THEN 2
+ WHEN COALESCE([o].[Priority], 0) = 2 THEN 3
+ ELSE 0
+END
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.verified.txt
new file mode 100644
index 0000000..55bbdc9
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.OrderByIntegerEnumExpansion.verified.txt
@@ -0,0 +1,8 @@
+SELECT [o].[Id], [o].[CustomerId], [o].[Priority], [o].[Status]
+FROM [Order] AS [o]
+ORDER BY CASE
+ WHEN COALESCE([o].[Priority], 0) = 0 THEN 1
+ WHEN COALESCE([o].[Priority], 0) = 1 THEN 2
+ WHEN COALESCE([o].[Priority], 0) = 2 THEN 3
+ ELSE 0
+END
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.DotNet10_0.verified.txt
new file mode 100644
index 0000000..32cfbd4
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [o].[Status] = 0 THEN CAST(0 AS bit)
+ WHEN [o].[Status] = 1 THEN CAST(1 AS bit)
+ WHEN [o].[Status] = 2 THEN CAST(0 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.DotNet9_0.verified.txt
new file mode 100644
index 0000000..32cfbd4
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [o].[Status] = 0 THEN CAST(0 AS bit)
+ WHEN [o].[Status] = 1 THEN CAST(1 AS bit)
+ WHEN [o].[Status] = 2 THEN CAST(0 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.verified.txt
new file mode 100644
index 0000000..32cfbd4
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectBooleanEnumExpansion.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [o].[Status] = 0 THEN CAST(0 AS bit)
+ WHEN [o].[Status] = 1 THEN CAST(1 AS bit)
+ WHEN [o].[Status] = 2 THEN CAST(0 AS bit)
+ ELSE CAST(0 AS bit)
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.DotNet10_0.verified.txt
new file mode 100644
index 0000000..a75a390
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.DotNet10_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [o].[Status] = 0 THEN N'Order Status: Pending'
+ WHEN [o].[Status] = 1 THEN N'Order Status: Approved'
+ WHEN [o].[Status] = 2 THEN N'Order Status: Rejected'
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.DotNet9_0.verified.txt
new file mode 100644
index 0000000..a75a390
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.DotNet9_0.verified.txt
@@ -0,0 +1,6 @@
+SELECT CASE
+ WHEN [o].[Status] = 0 THEN N'Order Status: Pending'
+ WHEN [o].[Status] = 1 THEN N'Order Status: Approved'
+ WHEN [o].[Status] = 2 THEN N'Order Status: Rejected'
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.verified.txt
new file mode 100644
index 0000000..35f7402
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumMethodWithParameter.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [o].[Status] = 0 THEN N'Order Status: Pending'
+ WHEN [o].[Status] = 1 THEN N'Order Status: Approved'
+ WHEN [o].[Status] = 2 THEN N'Order Status: Rejected'
+ ELSE NULL
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..47ae157
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.DotNet10_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [c].[PreferredPriority] = 0 THEN N'Low Priority'
+ WHEN [c].[PreferredPriority] = 1 THEN N'Medium Priority'
+ WHEN [c].[PreferredPriority] = 2 THEN N'High Priority'
+END
+FROM [OrderWithNavigation] AS [o]
+LEFT JOIN [Customer] AS [c] ON [o].[CustomerId] = [c].[Id]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..47ae157
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.DotNet9_0.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [c].[PreferredPriority] = 0 THEN N'Low Priority'
+ WHEN [c].[PreferredPriority] = 1 THEN N'Medium Priority'
+ WHEN [c].[PreferredPriority] = 2 THEN N'High Priority'
+END
+FROM [OrderWithNavigation] AS [o]
+LEFT JOIN [Customer] AS [c] ON [o].[CustomerId] = [c].[Id]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.verified.txt
new file mode 100644
index 0000000..a00cbba
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectEnumOnNavigationProperty.verified.txt
@@ -0,0 +1,8 @@
+SELECT CASE
+ WHEN [c].[PreferredPriority] = 0 THEN N'Low Priority'
+ WHEN [c].[PreferredPriority] = 1 THEN N'Medium Priority'
+ WHEN [c].[PreferredPriority] = 2 THEN N'High Priority'
+ ELSE NULL
+END
+FROM [OrderWithNavigation] AS [o]
+LEFT JOIN [Customer] AS [c] ON [o].[CustomerId] = [c].[Id]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..d812336
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.DotNet10_0.verified.txt
@@ -0,0 +1,6 @@
+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
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..d812336
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.DotNet9_0.verified.txt
@@ -0,0 +1,6 @@
+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
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.verified.txt
new file mode 100644
index 0000000..e490e64
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectExpandedEnumProperty.verified.txt
@@ -0,0 +1,7 @@
+SELECT CASE
+ WHEN [o].[Status] = 0 THEN N'Pending Review'
+ WHEN [o].[Status] = 1 THEN N'Approved'
+ WHEN [o].[Status] = 2 THEN N'Rejected'
+ ELSE NULL
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..b727d87
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.DotNet10_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT CASE
+ WHEN [o].[Priority] IS NOT NULL THEN CASE
+ WHEN [o].[Priority] = 0 THEN N'Low Priority'
+ WHEN [o].[Priority] = 1 THEN N'Medium Priority'
+ WHEN [o].[Priority] = 2 THEN N'High Priority'
+ END
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..b727d87
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.DotNet9_0.verified.txt
@@ -0,0 +1,8 @@
+SELECT CASE
+ WHEN [o].[Priority] IS NOT NULL THEN CASE
+ WHEN [o].[Priority] = 0 THEN N'Low Priority'
+ WHEN [o].[Priority] = 1 THEN N'Medium Priority'
+ WHEN [o].[Priority] = 2 THEN N'High Priority'
+ END
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.verified.txt
new file mode 100644
index 0000000..27dd172
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.SelectNullableEnumExpandedProperty.verified.txt
@@ -0,0 +1,10 @@
+SELECT CASE
+ WHEN [o].[Priority] IS NOT NULL THEN CASE
+ WHEN [o].[Priority] = 0 THEN N'Low Priority'
+ WHEN [o].[Priority] = 1 THEN N'Medium Priority'
+ WHEN [o].[Priority] = 2 THEN N'High Priority'
+ ELSE NULL
+ END
+ ELSE NULL
+END
+FROM [Order] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.cs
new file mode 100644
index 0000000..d31a85d
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExpandEnumMethodsTests.cs
@@ -0,0 +1,221 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
+using Microsoft.EntityFrameworkCore;
+using VerifyXunit;
+using Xunit;
+
+namespace EntityFrameworkCore.Projectables.FunctionalTests
+{
+ [UsesVerify]
+ public class ExpandEnumMethodsTests
+ {
+ public enum OrderStatus
+ {
+ [Display(Name = "Pending Review")]
+ Pending,
+
+ [Display(Name = "Approved")]
+ Approved,
+
+ [Display(Name = "Rejected")]
+ Rejected
+ }
+
+ public enum Priority
+ {
+ [Description("Low Priority")]
+ Low,
+
+ [Description("Medium Priority")]
+ Medium,
+
+ [Description("High Priority")]
+ High
+ }
+
+ public record Order
+ {
+ public int Id { get; set; }
+ public OrderStatus Status { get; set; }
+ public Priority? Priority { get; set; }
+ public Customer? Customer { get; set; }
+
+ [Projectable(ExpandEnumMethods = true)]
+ public string StatusName => Status.GetDisplayName();
+
+ [Projectable(ExpandEnumMethods = true, NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)]
+ public string? PriorityDescription => Priority.HasValue ? Priority.Value.GetDescription() : null;
+
+ [Projectable(ExpandEnumMethods = true)]
+ public bool IsApproved => Status.IsApproved();
+
+ [Projectable(ExpandEnumMethods = true)]
+ public int PrioritySortOrder => (Priority ?? ExpandEnumMethodsTests.Priority.Low).GetSortOrder();
+
+ [Projectable(ExpandEnumMethods = true)]
+ public string StatusWithPrefix => Status.GetDisplayNameWithPrefix("Order Status: ");
+ }
+
+ public record Customer
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } = "";
+ public Priority PreferredPriority { get; set; }
+ }
+
+ public record OrderWithNavigation
+ {
+ public int Id { get; set; }
+ public Customer? Customer { get; set; }
+
+ [Projectable(ExpandEnumMethods = true)]
+ public string CustomerPriorityDescription => Customer!.PreferredPriority.GetDescription();
+ }
+
+ [Fact]
+ public Task FilterOnExpandedEnumProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Where(x => x.StatusName == "Pending Review");
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SelectExpandedEnumProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.StatusName);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task OrderByExpandedEnumProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .OrderBy(x => x.StatusName);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SelectNullableEnumExpandedProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.PriorityDescription);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SelectEnumOnNavigationProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.CustomerPriorityDescription);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task FilterOnBooleanEnumExpansion()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Where(x => x.IsApproved);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SelectBooleanEnumExpansion()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.IsApproved);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task OrderByIntegerEnumExpansion()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .OrderBy(x => x.PrioritySortOrder);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task SelectEnumMethodWithParameter()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.StatusWithPrefix);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+ }
+
+ public static class EnumExtensions
+ {
+ public static string GetDisplayName(this TEnum value) where TEnum : struct, System.Enum
+ {
+ var type = value.GetType();
+ var memberInfo = type.GetMember(value.ToString())[0];
+ var displayAttribute = memberInfo.GetCustomAttributes(typeof(DisplayAttribute), false)
+ .OfType()
+ .FirstOrDefault();
+ return displayAttribute?.Name ?? value.ToString();
+ }
+
+ public static string GetDescription(this TEnum value) where TEnum : struct, System.Enum
+ {
+ var type = value.GetType();
+ var memberInfo = type.GetMember(value.ToString())[0];
+ var descriptionAttribute = memberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false)
+ .OfType()
+ .FirstOrDefault();
+ return descriptionAttribute?.Description ?? value.ToString();
+ }
+
+ public static bool IsApproved(this ExpandEnumMethodsTests.OrderStatus value)
+ {
+ return value == ExpandEnumMethodsTests.OrderStatus.Approved;
+ }
+
+ public static int GetSortOrder(this ExpandEnumMethodsTests.Priority value)
+ {
+ return value switch
+ {
+ ExpandEnumMethodsTests.Priority.Low => 1,
+ ExpandEnumMethodsTests.Priority.Medium => 2,
+ ExpandEnumMethodsTests.Priority.High => 3,
+ _ => 0
+ };
+ }
+
+ public static string GetDisplayNameWithPrefix(this ExpandEnumMethodsTests.OrderStatus value, string prefix)
+ {
+ return prefix + value.ToString();
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.DotNet10_0.verified.txt
new file mode 100644
index 0000000..d86f284
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.DotNet10_0.verified.txt
@@ -0,0 +1,3 @@
+SELECT [i].[Id]
+FROM [Item] AS [i]
+WHERE CONVERT(varchar(11), [i].[Id]) = '123'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.DotNet9_0.verified.txt
new file mode 100644
index 0000000..d86f284
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.DotNet9_0.verified.txt
@@ -0,0 +1,3 @@
+SELECT [i].[Id]
+FROM [Item] AS [i]
+WHERE CONVERT(varchar(11), [i].[Id]) = '123'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.verified.txt
new file mode 100644
index 0000000..5afc7a1
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.FilterOnExplicitInterfaceImplementation.verified.txt
@@ -0,0 +1,3 @@
+SELECT [i].[Id]
+FROM [Item] AS [i]
+WHERE CONVERT(varchar(11), [i].[Id]) = N'123'
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.DotNet10_0.verified.txt
new file mode 100644
index 0000000..4383f0e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT CONVERT(varchar(11), [i].[Id])
+FROM [Item] AS [i]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.DotNet9_0.verified.txt
new file mode 100644
index 0000000..4383f0e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT CONVERT(varchar(11), [i].[Id])
+FROM [Item] AS [i]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.verified.txt
new file mode 100644
index 0000000..4383f0e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.ProjectOverExplicitInterfaceImplementation.verified.txt
@@ -0,0 +1,2 @@
+SELECT CONVERT(varchar(11), [i].[Id])
+FROM [Item] AS [i]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.cs
new file mode 100644
index 0000000..b72fb50
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExplicitInterfaceImplementationTests.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
+using Microsoft.EntityFrameworkCore;
+using VerifyXunit;
+using Xunit;
+
+#nullable disable
+
+namespace EntityFrameworkCore.Projectables.FunctionalTests
+{
+ [UsesVerify]
+ public class ExplicitInterfaceImplementationTests
+ {
+ public interface IStringId
+ {
+ string Id { get; }
+ }
+
+ public class Item : IStringId
+ {
+ public int Id { get; set; }
+
+ // Explicit interface implementation without [Projectable]
+ // This tests that GetImplementingProperty handles this scenario
+ string IStringId.Id => Id.ToString();
+
+ [Projectable]
+ public string FormattedId => Id.ToString();
+ }
+
+ [Fact]
+ public Task ProjectOverExplicitInterfaceImplementation()
+ {
+ using var dbContext = new SampleDbContext- ();
+
+ var query = dbContext.Set
- ()
+ .Select(x => x.FormattedId);
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task FilterOnExplicitInterfaceImplementation()
+ {
+ using var dbContext = new SampleDbContext
- ();
+
+ var query = dbContext.Set
- ()
+ .Where(x => x.FormattedId == "123");
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/Entity.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/Entity.cs
new file mode 100644
index 0000000..4edc475
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/Entity.cs
@@ -0,0 +1,10 @@
+#if NET10_0_OR_GREATER
+namespace EntityFrameworkCore.Projectables.FunctionalTests.ExtensionMembers
+{
+ public class Entity
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ }
+}
+#endif
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/EntityExtensions.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/EntityExtensions.cs
new file mode 100644
index 0000000..f0c8c21
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/EntityExtensions.cs
@@ -0,0 +1,40 @@
+#if NET10_0_OR_GREATER
+namespace EntityFrameworkCore.Projectables.FunctionalTests.ExtensionMembers
+{
+ public static class EntityExtensions
+ {
+ extension(Entity e)
+ {
+ ///
+ /// Extension member property that doubles the entity's ID.
+ ///
+ [Projectable]
+ public int DoubleId => e.Id * 2;
+
+ ///
+ /// Extension member method that triples the entity's ID.
+ ///
+ [Projectable]
+ public int TripleId() => e.Id * 3;
+
+ ///
+ /// Extension member method that multiplies the entity's ID by a factor.
+ ///
+ [Projectable]
+ public int Multiply(int factor) => e.Id * factor;
+ }
+ }
+
+ public static class IntExtensions
+ {
+ extension(int i)
+ {
+ ///
+ /// Extension member property that squares an integer.
+ ///
+ [Projectable]
+ public int SquaredMember => i * i;
+ }
+ }
+}
+#endif
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodOnEntity.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodOnEntity.DotNet10_0.verified.txt
new file mode 100644
index 0000000..a7fa9cd
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodOnEntity.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id] * 3
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodOnEntity.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodOnEntity.verified.txt
new file mode 100644
index 0000000..a7fa9cd
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodOnEntity.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id] * 3
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodWithParameterOnEntity.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodWithParameterOnEntity.DotNet10_0.verified.txt
new file mode 100644
index 0000000..4f887bb
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodWithParameterOnEntity.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id] * 5
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodWithParameterOnEntity.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodWithParameterOnEntity.verified.txt
new file mode 100644
index 0000000..4f887bb
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.ExtensionMemberMethodWithParameterOnEntity.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id] * 5
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.cs
new file mode 100644
index 0000000..28e333f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionMembers/ExtensionMemberTests.cs
@@ -0,0 +1,43 @@
+#if NET10_0_OR_GREATER
+using System.Linq;
+using System.Threading.Tasks;
+using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
+using Microsoft.EntityFrameworkCore;
+using VerifyXunit;
+using Xunit;
+
+namespace EntityFrameworkCore.Projectables.FunctionalTests.ExtensionMembers
+{
+ ///
+ /// Tests for C# 14 extension member support.
+ /// These tests only run on .NET 10+ where extension members are supported.
+ /// Note: Extension properties cannot currently be used directly in LINQ expression trees (CS9296),
+ /// so only extension methods are tested here.
+ ///
+ [UsesVerify]
+ public class ExtensionMemberTests
+ {
+ [Fact]
+ public Task ExtensionMemberMethodOnEntity()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.TripleId());
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ExtensionMemberMethodWithParameterOnEntity()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(x => x.Multiply(5));
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+ }
+}
+#endif
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..a473c5f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [c].[Id] * 2
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..a473c5f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [c].[Id] * 2
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.verified.txt
new file mode 100644
index 0000000..a473c5f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultExplicitlyImplementedProperty.verified.txt
@@ -0,0 +1,2 @@
+SELECT [c].[Id] * 2
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..03753d3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT 49
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..03753d3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT 49
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.verified.txt
new file mode 100644
index 0000000..03753d3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverDefaultImplementedProperty.verified.txt
@@ -0,0 +1,2 @@
+SELECT 49
+FROM [Concrete] AS [c]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..5c98c81
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [o].[Id] / 2
+FROM [OtherConcrete] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.DotNet9_0.verified.txt
new file mode 100644
index 0000000..5c98c81
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [o].[Id] / 2
+FROM [OtherConcrete] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.verified.txt
new file mode 100644
index 0000000..5c98c81
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.ProjectOverExplicitlyImplementedProperty.verified.txt
@@ -0,0 +1,2 @@
+SELECT [o].[Id] / 2
+FROM [OtherConcrete] AS [o]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs
index d2a7f34..524c17d 100644
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/InheritedModelTests.cs
@@ -53,7 +53,21 @@ public abstract class Base : IBase
public virtual int SampleMethod() => 0;
}
- public class Concrete : Base
+ public interface IDefaultBase
+ {
+ int Explicit { get; }
+
+ [Projectable]
+ int Default => 49;
+ }
+
+ public interface IDefaultBaseImplementation : IDefaultBase, IBase
+ {
+ [Projectable]
+ int IDefaultBase.Explicit => Id * 2;
+ }
+
+ public class Concrete : Base, IDefaultBaseImplementation
{
[Projectable]
public override int SampleProperty => 1;
@@ -62,6 +76,12 @@ public class Concrete : Base
public override int SampleMethod() => 1;
}
+ public class OtherConcrete : Base, IDefaultBase
+ {
+ [Projectable]
+ int IDefaultBase.Explicit => Id / 2;
+ }
+
public class MoreConcrete : Concrete
{
}
@@ -130,6 +150,36 @@ public Task ProjectOverImplementedMethod()
return Verifier.Verify(query.ToQueryString());
}
+ [Fact]
+ public Task ProjectOverDefaultImplementedProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set().SelectDefaultProperty();
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ProjectOverExplicitlyImplementedProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set().SelectExplicitProperty();
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task ProjectOverDefaultExplicitlyImplementedProperty()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set().SelectExplicitProperty();
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
[Fact]
public Task ProjectOverProvider()
{
@@ -156,6 +206,14 @@ public static class ModelExtensions
public static IQueryable SelectComputedProperty(this IQueryable concretes)
where TConcrete : InheritedModelTests.IBase
=> concretes.Select(x => x.ComputedProperty);
+
+ public static IQueryable SelectDefaultProperty(this IQueryable concretes)
+ where TConcrete : InheritedModelTests.IDefaultBase
+ => concretes.Select(x => x.Default);
+
+ public static IQueryable SelectExplicitProperty(this IQueryable concretes)
+ where TConcrete : InheritedModelTests.IDefaultBase
+ => concretes.Select(x => x.Explicit);
public static IQueryable SelectComputedMethod(this IQueryable concretes)
where TConcrete : InheritedModelTests.IBase
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.DotNet10_0.verified.txt
new file mode 100644
index 0000000..e5e6812
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id], [e].[Id] + 10 AS [Result]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.DotNet9_0.verified.txt
new file mode 100644
index 0000000..e5e6812
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id], [e].[Id] + 10 AS [Result]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.verified.txt
new file mode 100644
index 0000000..e5e6812
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithIntParameter.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id], [e].[Id] + 10 AS [Result]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.DotNet10_0.verified.txt
new file mode 100644
index 0000000..d9749ce
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.DotNet10_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id], CAST(LEN(N'Hello_' + [e].[Name]) AS int) AS [Result]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.DotNet9_0.verified.txt
new file mode 100644
index 0000000..d9749ce
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.DotNet9_0.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id], CAST(LEN(N'Hello_' + [e].[Name]) AS int) AS [Result]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.verified.txt
new file mode 100644
index 0000000..d9749ce
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.MethodOverload_WithStringParameter.verified.txt
@@ -0,0 +1,2 @@
+SELECT [e].[Id], CAST(LEN(N'Hello_' + [e].[Name]) AS int) AS [Result]
+FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.cs
new file mode 100644
index 0000000..dbe24c4
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MethodOverloadsTests.cs
@@ -0,0 +1,48 @@
+using System.Linq;
+using System.Threading.Tasks;
+using EntityFrameworkCore.Projectables;
+using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
+using Microsoft.EntityFrameworkCore;
+using VerifyXunit;
+using Xunit;
+
+namespace EntityFrameworkCore.Projectables.FunctionalTests
+{
+ [UsesVerify]
+ public class MethodOverloadsTests
+ {
+ public record Entity
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } = "";
+
+ [Projectable]
+ public int Calculate(int x) => Id + x;
+
+ [Projectable]
+ public int Calculate(string prefix) => (prefix + Name).Length;
+ }
+
+ [Fact]
+ public Task MethodOverload_WithIntParameter()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(e => new { e.Id, Result = e.Calculate(10) });
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+
+ [Fact]
+ public Task MethodOverload_WithStringParameter()
+ {
+ using var dbContext = new SampleDbContext();
+
+ var query = dbContext.Set()
+ .Select(e => new { e.Id, Result = e.Calculate("Hello_") });
+
+ return Verifier.Verify(query.ToQueryString());
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/StatelessPropertyTests.FilterOnProjectableProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/StatelessPropertyTests.FilterOnProjectableProperty.verified.txt
deleted file mode 100644
index 7244f2c..0000000
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/StatelessPropertyTests.FilterOnProjectableProperty.verified.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-SELECT [e].[Id]
-FROM [Entity] AS [e]
-WHERE 0 = 1
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/StatelessPropertyTests.SelectProjectableProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/StatelessPropertyTests.SelectProjectableProperty.verified.txt
deleted file mode 100644
index 7ff8a35..0000000
--- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/StatelessPropertyTests.SelectProjectableProperty.verified.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-SELECT 0
-FROM [Entity] AS [e]
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BooleanSimpleTernary_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BooleanSimpleTernary_WithRewriteSupport_IsBeingRewritten.verified.txt
index 2e61855..8fb3344 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BooleanSimpleTernary_WithRewriteSupport_IsBeingRewritten.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.BooleanSimpleTernary_WithRewriteSupport_IsBeingRewritten.verified.txt
@@ -8,7 +8,7 @@ using Foo;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class Foo_C_Test
+ static class Foo_C_Test_P0_object
{
static global::System.Linq.Expressions.Expression> Expression()
{
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.Cast.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.Cast.verified.txt
index 0035477..cf931bf 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.Cast.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.Cast.verified.txt
@@ -6,7 +6,7 @@ using Projectables.Repro;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class Projectables_Repro_SomeExtensions_AsSomeResult
+ static class Projectables_Repro_SomeExtensions_AsSomeResult_P0_Projectables_Repro_SomeEntity
{
static global::System.Linq.Expressions.Expression> Expression()
{
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DeclarationTypeNamesAreGettingFullyQualified.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DeclarationTypeNamesAreGettingFullyQualified.verified.txt
index dd9cb44..cec10d1 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DeclarationTypeNamesAreGettingFullyQualified.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DeclarationTypeNamesAreGettingFullyQualified.verified.txt
@@ -9,7 +9,7 @@ using Foo;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class Foo_EntityExtensions_Entity_Something
+ static class Foo_EntityExtensions_Entity_Something_P0_Foo_EntityExtensions_Entity
{
static global::System.Linq.Expressions.Expression> Expression()
{
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultExplicitInterfaceMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultExplicitInterfaceMember.verified.txt
new file mode 100644
index 0000000..f4fc80e
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultExplicitInterfaceMember.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _IDefaultBaseImplementation_IDefaultBase__Default
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::IDefaultBaseImplementation @this) => @this.ComputedProperty * 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultInterfaceMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultInterfaceMember.verified.txt
new file mode 100644
index 0000000..8bf6f22
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultInterfaceMember.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _IDefaultBase_Default
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::IDefaultBase @this) => @this.ComputedProperty * 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultValuesGetRemoved.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultValuesGetRemoved.verified.txt
index 0cb3392..b0c3e64 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultValuesGetRemoved.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DefaultValuesGetRemoved.verified.txt
@@ -5,7 +5,7 @@ using EntityFrameworkCore.Projectables;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class _Foo_Calculate
+ static class _Foo_Calculate_P0_int
{
static global::System.Linq.Expressions.Expression> Expression()
{
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DictionaryIndexInitializer_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DictionaryIndexInitializer_IsBeingRewritten.verified.txt
new file mode 100644
index 0000000..53ba6e3
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DictionaryIndexInitializer_IsBeingRewritten.verified.txt
@@ -0,0 +1,29 @@
+//
+#nullable disable
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_ToDictionary_P0_Foo_EntityExtensions_Entity
+ {
+ static global::System.Linq.Expressions.Expression>> Expression()
+ {
+ return (global::Foo.EntityExtensions.Entity entity) => new Dictionary
+ {
+ {
+ "FullName",
+ entity.FullName ?? "N/A"
+ },
+ {
+ "Id",
+ entity.Id.ToString()
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DictionaryObjectInitializer_PreservesCollectionInitializerSyntax.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DictionaryObjectInitializer_PreservesCollectionInitializerSyntax.verified.txt
new file mode 100644
index 0000000..638405f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.DictionaryObjectInitializer_PreservesCollectionInitializerSyntax.verified.txt
@@ -0,0 +1,25 @@
+//
+#nullable disable
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_ToDictionary_P0_Foo_EntityExtensions_Entity
+ {
+ static global::System.Linq.Expressions.Expression>> Expression()
+ {
+ return (global::Foo.EntityExtensions.Entity entity) => new Dictionary
+ {
+ {
+ "FullName",
+ entity.FullName ?? "N/A"
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.EnumAccessor.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.EnumAccessor.verified.txt
index ce25012..d108871 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.EnumAccessor.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.EnumAccessor.verified.txt
@@ -5,7 +5,7 @@ using EntityFrameworkCore.Projectables;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class _SomeExtensions_Test
+ static class _SomeExtensions_Test_P0_SomeFlag
{
static global::System.Linq.Expressions.Expression> Expression()
{
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsOnNavigationProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsOnNavigationProperty.verified.txt
new file mode 100644
index 0000000..966236f
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsOnNavigationProperty.verified.txt
@@ -0,0 +1,18 @@
+//
+#nullable disable
+using System;
+using System.ComponentModel.DataAnnotations;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_OrderItem_OrderStatusName
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.OrderItem @this) => (@this.Order.Status == global::Foo.OrderStatus.Pending ? global::Foo.EnumExtensions.GetDisplayName(global::Foo.OrderStatus.Pending) : @this.Order.Status == global::Foo.OrderStatus.Approved ? global::Foo.EnumExtensions.GetDisplayName(global::Foo.OrderStatus.Approved) : null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsReturningBoolean.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsReturningBoolean.verified.txt
new file mode 100644
index 0000000..318c241
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsReturningBoolean.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Entity_IsStatusApproved
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => (@this.Status == global::Foo.Status.Pending ? global::Foo.EnumExtensions.IsApproved(global::Foo.Status.Pending) : @this.Status == global::Foo.Status.Approved ? global::Foo.EnumExtensions.IsApproved(global::Foo.Status.Approved) : @this.Status == global::Foo.Status.Rejected ? global::Foo.EnumExtensions.IsApproved(global::Foo.Status.Rejected) : default(bool));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsReturningInteger.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsReturningInteger.verified.txt
new file mode 100644
index 0000000..7090676
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsReturningInteger.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Entity_PrioritySortOrder
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => (@this.Priority == global::Foo.Priority.Low ? global::Foo.EnumExtensions.GetSortOrder(global::Foo.Priority.Low) : @this.Priority == global::Foo.Priority.Medium ? global::Foo.EnumExtensions.GetSortOrder(global::Foo.Priority.Medium) : @this.Priority == global::Foo.Priority.High ? global::Foo.EnumExtensions.GetSortOrder(global::Foo.Priority.High) : default(int));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithDescriptionAttribute.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithDescriptionAttribute.verified.txt
new file mode 100644
index 0000000..8df7bf2
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithDescriptionAttribute.verified.txt
@@ -0,0 +1,18 @@
+//
+#nullable disable
+using System;
+using System.ComponentModel;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Entity_StatusDescription
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => (@this.Status == global::Foo.Status.Pending ? global::Foo.EnumExtensions.GetDescription(global::Foo.Status.Pending) : @this.Status == global::Foo.Status.Approved ? global::Foo.EnumExtensions.GetDescription(global::Foo.Status.Approved) : @this.Status == global::Foo.Status.Rejected ? global::Foo.EnumExtensions.GetDescription(global::Foo.Status.Rejected) : null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithDisplayAttribute.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithDisplayAttribute.verified.txt
new file mode 100644
index 0000000..72bf11c
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithDisplayAttribute.verified.txt
@@ -0,0 +1,18 @@
+//
+#nullable disable
+using System;
+using System.ComponentModel.DataAnnotations;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Entity_MyEnumName
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => (@this.MyValue == global::Foo.CustomEnum.Value1 ? global::Foo.EnumExtensions.GetDisplayName(global::Foo.CustomEnum.Value1) : @this.MyValue == global::Foo.CustomEnum.Value2 ? global::Foo.EnumExtensions.GetDisplayName(global::Foo.CustomEnum.Value2) : null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithMultipleParameters.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithMultipleParameters.verified.txt
new file mode 100644
index 0000000..f626eea
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithMultipleParameters.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Entity_FormattedStatus
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => (@this.Status == global::Foo.Status.Pending ? global::Foo.EnumExtensions.Format(global::Foo.Status.Pending, "[", "]") : @this.Status == global::Foo.Status.Approved ? global::Foo.EnumExtensions.Format(global::Foo.Status.Approved, "[", "]") : @this.Status == global::Foo.Status.Rejected ? global::Foo.EnumExtensions.Format(global::Foo.Status.Rejected, "[", "]") : null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithNullableEnum.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithNullableEnum.verified.txt
new file mode 100644
index 0000000..a38b302
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithNullableEnum.verified.txt
@@ -0,0 +1,18 @@
+//
+#nullable disable
+using System;
+using System.ComponentModel.DataAnnotations;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Entity_MyEnumName
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => @this.MyValue.HasValue ? (@this.MyValue.Value == global::Foo.CustomEnum.First ? global::Foo.EnumExtensions.GetDisplayName(global::Foo.CustomEnum.First) : @this.MyValue.Value == global::Foo.CustomEnum.Second ? global::Foo.EnumExtensions.GetDisplayName(global::Foo.CustomEnum.Second) : null) : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithParameter.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithParameter.verified.txt
new file mode 100644
index 0000000..d1332ed
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExpandEnumMethodsWithParameter.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Entity_StatusWithPrefix
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => (@this.Status == global::Foo.Status.Pending ? global::Foo.EnumExtensions.GetDisplayNameWithPrefix(global::Foo.Status.Pending, "Status: ") : @this.Status == global::Foo.Status.Approved ? global::Foo.EnumExtensions.GetDisplayNameWithPrefix(global::Foo.Status.Approved, "Status: ") : @this.Status == global::Foo.Status.Rejected ? global::Foo.EnumExtensions.GetDisplayNameWithPrefix(global::Foo.Status.Rejected, "Status: ") : null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceImplementation.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceImplementation.verified.txt
new file mode 100644
index 0000000..815c681
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceImplementation.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_Item_FormattedId
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Item @this) => ((global::Foo.IStringId)@this).Id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceMember.verified.txt
new file mode 100644
index 0000000..d405def
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExplicitInterfaceMember.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _Concrete_IBase__ComputedProperty
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Concrete @this) => @this.Id + 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethod.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethod.DotNet10_0.verified.txt
new file mode 100644
index 0000000..e0d8932
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethod.DotNet10_0.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_TripleId_P0_Foo_Entity
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => @this.Id * 3;
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethod.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethod.verified.txt
new file mode 100644
index 0000000..e0d8932
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethod.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_TripleId_P0_Foo_Entity
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => @this.Id * 3;
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethodWithParameters.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethodWithParameters.DotNet10_0.verified.txt
new file mode 100644
index 0000000..837945b
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethodWithParameters.DotNet10_0.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_Multiply_P0_Foo_Entity_P1_int
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this, int factor) => @this.Id * factor;
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethodWithParameters.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethodWithParameters.verified.txt
new file mode 100644
index 0000000..837945b
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberMethodWithParameters.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_Multiply_P0_Foo_Entity_P1_int
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this, int factor) => @this.Id * factor;
+ }
+ }
+}
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberOnPrimitive.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberOnPrimitive.DotNet10_0.verified.txt
new file mode 100644
index 0000000..f6a60da
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberOnPrimitive.DotNet10_0.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _IntExtensions_Squared
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (int @this) => @this * @this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberOnPrimitive.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberOnPrimitive.verified.txt
new file mode 100644
index 0000000..f6a60da
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberOnPrimitive.verified.txt
@@ -0,0 +1,16 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class _IntExtensions_Squared
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (int @this) => @this * @this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberProperty.DotNet10_0.verified.txt
new file mode 100644
index 0000000..0d29557
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberProperty.DotNet10_0.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_DoubleId
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => @this.Id * 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberProperty.verified.txt
new file mode 100644
index 0000000..0d29557
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberProperty.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_DoubleId
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => @this.Id * 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberWithMemberAccess.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberWithMemberAccess.DotNet10_0.verified.txt
new file mode 100644
index 0000000..5707b4d
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberWithMemberAccess.DotNet10_0.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_IdAndName
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => @this.Id + ": " + @this.Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberWithMemberAccess.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberWithMemberAccess.verified.txt
new file mode 100644
index 0000000..5707b4d
--- /dev/null
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ExtensionMemberWithMemberAccess.verified.txt
@@ -0,0 +1,17 @@
+//
+#nullable disable
+using System;
+using EntityFrameworkCore.Projectables;
+using Foo;
+
+namespace EntityFrameworkCore.Projectables.Generated
+{
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+ static class Foo_EntityExtensions_IdAndName
+ {
+ static global::System.Linq.Expressions.Expression> Expression()
+ {
+ return (global::Foo.Entity @this) => @this.Id + ": " + @this.Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericMethods_AreRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericMethods_AreRewritten.verified.txt
index 5468c2a..973cde3 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericMethods_AreRewritten.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericMethods_AreRewritten.verified.txt
@@ -9,7 +9,7 @@ using Foo;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class Foo_EntityExtensions_EnforceString
+ static class Foo_EntityExtensions_EnforceString_P0_T
{
static global::System.Linq.Expressions.Expression> Expression()
where T : unmanaged
diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt
index 847f758..0993caa 100644
--- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt
+++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.GenericNullableReferenceTypesAreBeingEliminated.verified.txt
@@ -9,7 +9,7 @@ using Foo;
namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
- static class Foo_C_NextFoo
+ static class Foo_C_NextFoo_P0_System_Collections_Generic_List_object__P1_System_Collections_Generic_List_int__
{
static global::System.Linq.Expressions.Expression, global::System.Collections.Generic.List, global::System.Collections.Generic.List