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>> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MethodOverloads_WithDifferentParameterCounts.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MethodOverloads_WithDifferentParameterCounts.verified.txt new file mode 100644 index 0000000..f383978 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MethodOverloads_WithDifferentParameterCounts.verified.txt @@ -0,0 +1,37 @@ +[ +// +#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_C_Method_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.C @this, int x) => x; + } + } +} + +// +#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_C_Method_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.C @this, int x, int y) => x + y; + } + } +} +] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MethodOverloads_WithDifferentParameterTypes.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MethodOverloads_WithDifferentParameterTypes.verified.txt new file mode 100644 index 0000000..3113734 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MethodOverloads_WithDifferentParameterTypes.verified.txt @@ -0,0 +1,37 @@ +[ +// +#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_C_Method_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.C @this, int x) => x; + } + } +} + +// +#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_C_Method_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.C @this, string s) => s.Length; + } + } +} +] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MixPrimaryConstructorAndProperties.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MixPrimaryConstructorAndProperties.verified.txt index bb4a054..84bf825 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MixPrimaryConstructorAndProperties.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.MixPrimaryConstructorAndProperties.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.NullConditionalNullCoalesceTypeConversion.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullConditionalNullCoalesceTypeConversion.verified.txt index 90ab28c..f366397 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullConditionalNullCoalesceTypeConversion.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullConditionalNullCoalesceTypeConversion.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_SomeNumber + static class _Foo_SomeNumber_P0_Foo { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt index 3d8995b..0153a0f 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithIgnoreSupport_IsBeingRewritten.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_GetFirstRelatedIgnoreNulls + static class Foo_EntityExtensions_GetFirstRelatedIgnoreNulls_P0_Foo_EntityExtensions_Entity { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt index 7c950f2..010f995 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementAndMemberBinding_WithRewriteSupport_IsBeingRewritten.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_GetFirstRelatedIgnoreNulls + static class Foo_EntityExtensions_GetFirstRelatedIgnoreNulls_P0_Foo_EntityExtensions_Entity { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt index 0420fa1..aff9e8b 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_WithIgnoreSupport_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_GetFirst + static class Foo_C_GetFirst_P0_string { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt index 76ccb92..c881b5d 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableElementBinding_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_GetFirst + static class Foo_C_GetFirst_P0_string { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt index b8ce72b..97ef48d 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithIgnoreSupport_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_GetLength + static class Foo_C_GetLength_P0_string { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt index 402a7e3..4b30f5d 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableMemberBinding_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_GetLength + static class Foo_C_GetLength_P0_string { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.verified.txt index 19c08b4..fc9af02 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableParameters_WithRewriteSupport_IsBeingRewritten.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_GetFirstName + static class Foo_EntityExtensions_GetFirstName_P0_Foo_EntityExtensions_Entity { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.verified.txt index aa6fd95..48f6a5f 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypeCastOperatorGetsEliminated.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_NullableReferenceType + static class Foo_C_NullableReferenceType_P0_object { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.verified.txt index 88826b1..f880d94 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableReferenceTypesAreBeingEliminated.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_NextFoo + static class Foo_C_NextFoo_P0_object_P1_int_ { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt index 827dcb3..fe0230a 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithIgnoreSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithIgnoreSupport_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_GetFirst + static class Foo_C_GetFirst_P0_string { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt index a6732e1..d34d367 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_WithRewriteSupport_IsBeingRewritten.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableSimpleElementBinding_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_GetFirst + static class Foo_C_GetFirst_P0_string { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.verified.txt index 3f93eac..8cfc5e5 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.NullableValueCastOperatorsPersist.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_NullableValueType + static class Foo_C_NullableValueType_P0_object { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ParamsModifiedGetsRemoved.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ParamsModifiedGetsRemoved.verified.txt index 3f5152f..4231778 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ParamsModifiedGetsRemoved.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ParamsModifiedGetsRemoved.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_First + static class _Foo_First_P0_int__ { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithMultipleArguments.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithMultipleArguments.verified.txt index e6f1903..6a50a32 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithMultipleArguments.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithMultipleArguments.verified.txt @@ -7,7 +7,7 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_C_Foo + static class Foo_C_Foo_P0_int_P1_string_P2_object { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithSingleArgument.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithSingleArgument.verified.txt index 38b5559..864fb95 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithSingleArgument.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableComputedMethodWithSingleArgument.verified.txt @@ -7,7 +7,7 @@ using Foo; namespace EntityFrameworkCore.Projectables.Generated { [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class Foo_C_Foo + static class Foo_C_Foo_P0_int { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod.verified.txt index e62ff27..8e8012f 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod.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_Foo + static class Foo_C_Foo_P0_Foo_D { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod2.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod2.verified.txt index 4211a85..b17f73b 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod2.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod2.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_Foo + static class Foo_C_Foo_P0_int { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod3.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod3.verified.txt index 0bd0865..bf3ca9c 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod3.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod3.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_Foo1 + static class Foo_C_Foo1_P0_int { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod4.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod4.verified.txt index 636c199..03de978 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod4.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableExtensionMethod4.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_Foo1 + static class Foo_C_Foo1_P0_object { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.StaticMethodWithParameters.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.StaticMethodWithParameters.verified.txt index 6bfafc8..f5a9570 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.StaticMethodWithParameters.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.StaticMethodWithParameters.verified.txt @@ -8,7 +8,7 @@ using EntityFrameworkCore.Projectables; namespace EntityFrameworkCore.Projectables.Generated { [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class _Foo_Zero + static class _Foo_Zero_P0_int { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithConstantPattern.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithConstantPattern.verified.txt index 00c6d5e..e906cc7 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithConstantPattern.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithConstantPattern.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_SomeNumber + static class _Foo_SomeNumber_P0_int { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithTypePattern.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithTypePattern.verified.txt index dcb1ec1..c9efd3c 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithTypePattern.verified.txt +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.SwitchExpressionWithTypePattern.verified.txt @@ -5,7 +5,7 @@ using EntityFrameworkCore.Projectables; namespace EntityFrameworkCore.Projectables.Generated { [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - static class _ItemMapper_ToData + static class _ItemMapper_ToData_P0_Item { static global::System.Linq.Expressions.Expression> Expression() { diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index deb266a..4bda5a1 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -134,8 +135,8 @@ public Task SimpleProjectableComputedInNestedClassProperty() using System; using EntityFrameworkCore.Projectables; namespace Foo { - class C { - class D { + public class C { + public class D { public int Bar { get; set; } [Projectable] @@ -1901,86 +1902,874 @@ public Task GenericTypesWithConstraints() return Verifier.Verify(result.GeneratedTrees[0].ToString()); } + + [Fact] + public Task DictionaryIndexInitializer_IsBeingRewritten() + { + // lang=csharp + var compilation = CreateCompilation(@" +using System; +using System.Linq; +using System.Collections.Generic; +using EntityFrameworkCore.Projectables; - #region Helpers +namespace Foo { + public static class EntityExtensions + { + public record Entity + { + public int Id { get; set; } + public string? FullName { get; set; } + } + + [Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)] + public static Dictionary ToDictionary(this Entity entity) + => new Dictionary + { + [""FullName""] = entity.FullName ?? ""N/A"", + [""Id""] = entity.Id.ToString(), + }; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task DictionaryObjectInitializer_PreservesCollectionInitializerSyntax() + { + // lang=csharp + var compilation = CreateCompilation(@" +using System; +using System.Linq; +using System.Collections.Generic; +using EntityFrameworkCore.Projectables; - Compilation CreateCompilation(string source, bool expectedToCompile = true) +namespace Foo { + public static class EntityExtensions + { + public record Entity { - var references = Basic.Reference.Assemblies. -#if NET10_0 - Net100 -#elif NET9_0 - Net90 -#elif NET8_0 - Net80 -#endif - .References.All.ToList(); + public int Id { get; set; } + public string? FullName { get; set; } + } + + [Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Rewrite)] + public static Dictionary ToDictionary(this Entity entity) + => new Dictionary + { + { ""FullName"", entity.FullName ?? ""N/A"" } + }; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task MethodOverloads_WithDifferentParameterTypes() + { + // lang=csharp + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; +namespace Foo { + class C { + [Projectable] + public int Method(int x) => x; + + [Projectable] + public int Method(string s) => s.Length; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Equal(2, result.GeneratedTrees.Length); - references.Add(MetadataReference.CreateFromFile(typeof(ProjectableAttribute).Assembly.Location)); + // Verify both overloads are generated with distinct names + var generatedFiles = result.GeneratedTrees.Select(t => t.FilePath).ToList(); + Assert.Contains(generatedFiles, f => f.Contains("Method_P0_int.g.cs")); + Assert.Contains(generatedFiles, f => f.Contains("Method_P0_string.g.cs")); - var compilation = CSharpCompilation.Create("compilation", - new[] { CSharpSyntaxTree.ParseText(source) }, - references, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + return Verifier.Verify(result.GeneratedTrees.Select(t => t.ToString())); + } + + [Fact] + public Task MethodOverloads_WithDifferentParameterCounts() + { + // lang=csharp + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; +namespace Foo { + class C { + [Projectable] + public int Method(int x) => x; + + [Projectable] + public int Method(int x, int y) => x + y; + } +} +"); -#if DEBUG + var result = RunGenerator(compilation); - if (expectedToCompile) - { - var compilationDiagnostics = compilation.GetDiagnostics(); + Assert.Empty(result.Diagnostics); + Assert.Equal(2, result.GeneratedTrees.Length); + + // Verify both overloads are generated with distinct names + var generatedFiles = result.GeneratedTrees.Select(t => t.FilePath).ToList(); + Assert.Contains(generatedFiles, f => f.Contains("Method_P0_int.g.cs")); + Assert.Contains(generatedFiles, f => f.Contains("Method_P0_int_P1_int.g.cs")); - if (!compilationDiagnostics.IsEmpty) - { - _testOutputHelper.WriteLine($"Original compilation diagnostics produced:"); + return Verifier.Verify(result.GeneratedTrees.Select(t => t.ToString())); + } - foreach (var diagnostic in compilationDiagnostics) - { - _testOutputHelper.WriteLine($" > " + diagnostic.ToString()); - } +#if NET10_0_OR_GREATER + [Fact] + public Task ExtensionMemberProperty() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; - if (compilationDiagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) - { - Debug.Fail("Compilation diagnostics produced"); - } - } - } -#endif +namespace Foo { + class Entity { + public int Id { get; set; } + } + + static class EntityExtensions { + extension(Entity e) { + [Projectable] + public int DoubleId => e.Id * 2; + } + } +} +"); - return compilation; + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); } - private GeneratorDriverRunResult RunGenerator(Compilation compilation) + [Fact] + public Task ExtensionMemberMethod() { - _testOutputHelper.WriteLine("Running generator and updating compilation..."); + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; - var subject = new ProjectionExpressionGenerator(); - var driver = CSharpGeneratorDriver - .Create(subject) - .RunGenerators(compilation); +namespace Foo { + class Entity { + public int Id { get; set; } + } + + static class EntityExtensions { + extension(Entity e) { + [Projectable] + public int TripleId() => e.Id * 3; + } + } +} +"); - var result = driver.GetRunResult(); + var result = RunGenerator(compilation); - if (result.Diagnostics.IsEmpty) - { - _testOutputHelper.WriteLine("Run did not produce diagnostics"); - } - else - { - _testOutputHelper.WriteLine($"Diagnostics produced:"); + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); - foreach (var diagnostic in result.Diagnostics) - { - _testOutputHelper.WriteLine($" > " + diagnostic.ToString()); - } - } + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } - foreach (var newSyntaxTree in result.GeneratedTrees) - { - _testOutputHelper.WriteLine($"Produced syntax tree with path produced: {newSyntaxTree.FilePath}"); - _testOutputHelper.WriteLine(newSyntaxTree.GetText().ToString()); + [Fact] + public Task ExtensionMemberMethodWithParameters() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Entity { + public int Id { get; set; } + } + + static class EntityExtensions { + extension(Entity e) { + [Projectable] + public int Multiply(int factor) => e.Id * factor; + } + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExtensionMemberOnPrimitive() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +static class IntExtensions { + extension(int i) { + [Projectable] + public int Squared => i * i; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExtensionMemberWithMemberAccess() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Entity { + public int Id { get; set; } + public string Name { get; set; } + } + + static class EntityExtensions { + extension(Entity e) { + [Projectable] + public string IdAndName => e.Id + "": "" + e.Name; + } + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } +#endif + + [Fact] + public Task ExpandEnumMethodsWithDisplayAttribute() + { + var compilation = CreateCompilation(@" +using System; +using System.ComponentModel.DataAnnotations; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum CustomEnum + { + [Display(Name = ""Value 1"")] + Value1, + + [Display(Name = ""Value 2"")] + Value2, + } + + public static class EnumExtensions + { + public static string GetDisplayName(this CustomEnum value) + { + return value.ToString(); + } + } + + public record Entity + { + public int Id { get; set; } + public CustomEnum MyValue { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public string MyEnumName => MyValue.GetDisplayName(); + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExpandEnumMethodsWithNullableEnum() + { + var compilation = CreateCompilation(@" +using System; +using System.ComponentModel.DataAnnotations; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum CustomEnum + { + [Display(Name = ""First Value"")] + First, + + [Display(Name = ""Second Value"")] + Second, + } + + public static class EnumExtensions + { + public static string GetDisplayName(this CustomEnum value) + { + return value.ToString(); + } + } + + public record Entity + { + public int Id { get; set; } + public CustomEnum? MyValue { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public string MyEnumName => MyValue.HasValue ? MyValue.Value.GetDisplayName() : null; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExpandEnumMethodsWithDescriptionAttribute() + { + var compilation = CreateCompilation(@" +using System; +using System.ComponentModel; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum Status + { + [Description(""The item is pending"")] + Pending, + + [Description(""The item is approved"")] + Approved, + + [Description(""The item is rejected"")] + Rejected, + } + + public static class EnumExtensions + { + public static string GetDescription(this Status value) + { + return value.ToString(); + } + } + + public record Entity + { + public int Id { get; set; } + public Status Status { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public string StatusDescription => Status.GetDescription(); + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExpandEnumMethodsOnNavigationProperty() + { + var compilation = CreateCompilation(@" +using System; +using System.ComponentModel.DataAnnotations; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum OrderStatus + { + [Display(Name = ""Pending Review"")] + Pending, + + [Display(Name = ""Approved"")] + Approved, + } + + public static class EnumExtensions + { + public static string GetDisplayName(this OrderStatus value) + { + return value.ToString(); + } + } + + public record Order + { + public int Id { get; set; } + public OrderStatus Status { get; set; } + } + + public record OrderItem + { + public int Id { get; set; } + public Order Order { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public string OrderStatusName => Order.Status.GetDisplayName(); + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExpandEnumMethodsReturningBoolean() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum Status + { + Pending, + Approved, + Rejected, + } + + public static class EnumExtensions + { + public static bool IsApproved(this Status value) + { + return value == Status.Approved; + } + } + + public record Entity + { + public int Id { get; set; } + public Status Status { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public bool IsStatusApproved => Status.IsApproved(); + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExpandEnumMethodsReturningInteger() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum Priority + { + Low, + Medium, + High, + } + + public static class EnumExtensions + { + public static int GetSortOrder(this Priority value) + { + return (int)value; + } + } + + public record Entity + { + public int Id { get; set; } + public Priority Priority { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public int PrioritySortOrder => Priority.GetSortOrder(); + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExpandEnumMethodsWithParameter() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum Status + { + Pending, + Approved, + Rejected, + } + + public static class EnumExtensions + { + public static string GetDisplayNameWithPrefix(this Status value, string prefix) + { + return prefix + value.ToString(); + } + } + + public record Entity + { + public int Id { get; set; } + public Status Status { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public string StatusWithPrefix => Status.GetDisplayNameWithPrefix(""Status: ""); + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExpandEnumMethodsWithMultipleParameters() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public enum Status + { + Pending, + Approved, + Rejected, + } + + public static class EnumExtensions + { + public static string Format(this Status value, string prefix, string suffix) + { + return prefix + value.ToString() + suffix; + } + } + + public record Entity + { + public int Id { get; set; } + public Status Status { get; set; } + + [Projectable(ExpandEnumMethods = true)] + public string FormattedStatus => Status.Format(""["", ""]""); + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExplicitInterfaceMember() + { + var compilation = CreateCompilation( + """ + using System; + using EntityFrameworkCore.Projectables; + + public interface IBase + { + int ComputedProperty { get; } + } + + public class Concrete : IBase + { + public int Id { get; } + + [Projectable] + int IBase.ComputedProperty => Id + 1; + } + """); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task DefaultInterfaceMember() + { + var compilation = CreateCompilation( + """ + using System; + using EntityFrameworkCore.Projectables; + + public interface IBase + { + int Id { get; } + int ComputedProperty { get; } + int ComputedMethod(); + } + + public interface IDefaultBase : IBase + { + [Projectable] + int Default => ComputedProperty * 2; + } + """); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task DefaultExplicitInterfaceMember() + { + var compilation = CreateCompilation( + """ + using System; + using EntityFrameworkCore.Projectables; + + public interface IBase + { + int Id { get; } + int ComputedProperty { get; } + int ComputedMethod(); + } + + public interface IDefaultBase + { + int Default { get; } + } + + public interface IDefaultBaseImplementation : IDefaultBase, IBase + { + [Projectable] + int IDefaultBase.Default => ComputedProperty * 2; + } + """); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ExplicitInterfaceImplementation() + { + var compilation = CreateCompilation(@" +using System; +using EntityFrameworkCore.Projectables; + +namespace Foo { + public interface IStringId + { + string Id { get; } + } + + public class Item : IStringId + { + public int Id { get; set; } + + // Explicit interface implementation without [Projectable] + string IStringId.Id => Id.ToString(); + + [Projectable] + public string FormattedId => ((IStringId)this).Id; + } +} +"); + + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + #region Helpers + + Compilation CreateCompilation([StringSyntax("csharp")] string source, bool expectedToCompile = true) + { + var references = Basic.Reference.Assemblies. +#if NET10_0 + Net100 +#elif NET9_0 + Net90 +#elif NET8_0 + Net80 +#endif + .References.All.ToList(); + + references.Add(MetadataReference.CreateFromFile(typeof(ProjectableAttribute).Assembly.Location)); + + var compilation = CSharpCompilation.Create("compilation", + new[] { CSharpSyntaxTree.ParseText(source) }, + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + +#if DEBUG + + if (expectedToCompile) + { + var compilationDiagnostics = compilation.GetDiagnostics(); + + if (!compilationDiagnostics.IsEmpty) + { + _testOutputHelper.WriteLine($"Original compilation diagnostics produced:"); + + foreach (var diagnostic in compilationDiagnostics) + { + _testOutputHelper.WriteLine($" > " + diagnostic.ToString()); + } + + if (compilationDiagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) + { + Debug.Fail("Compilation diagnostics produced"); + } + } + } +#endif + + return compilation; + } + + private GeneratorDriverRunResult RunGenerator(Compilation compilation) + { + _testOutputHelper.WriteLine("Running generator and updating compilation..."); + + var subject = new ProjectionExpressionGenerator(); + var driver = CSharpGeneratorDriver + .Create(subject) + .RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _); + + var result = driver.GetRunResult(); + + if (result.Diagnostics.IsEmpty) + { + _testOutputHelper.WriteLine("Run did not produce diagnostics"); + } + else + { + _testOutputHelper.WriteLine("Diagnostics produced:"); + + foreach (var diagnostic in result.Diagnostics) + { + _testOutputHelper.WriteLine(" > " + diagnostic); + } + } + + foreach (var newSyntaxTree in result.GeneratedTrees) + { + _testOutputHelper.WriteLine($"Produced syntax tree with path produced: {newSyntaxTree.FilePath}"); + _testOutputHelper.WriteLine(newSyntaxTree.GetText().ToString()); + } + + // Verify that the generated code compiles without errors + var hasGeneratorErrors = result.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error); + if (!hasGeneratorErrors && result.GeneratedTrees.Length > 0) + { + _testOutputHelper.WriteLine("Checking that generated code compiles..."); + + var compilationErrors = outputCompilation + .GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + + if (compilationErrors.Count > 0) + { + _testOutputHelper.WriteLine("Generated code produced compilation errors:"); + foreach (var error in compilationErrors) + { + _testOutputHelper.WriteLine(" > " + error); + } + } + + Assert.Empty(compilationErrors); } - return driver.GetRunResult(); + return result; } #endregion diff --git a/tests/EntityFrameworkCore.Projectables.Tests/Extensions/TypeExtensionTests.cs b/tests/EntityFrameworkCore.Projectables.Tests/Extensions/TypeExtensionTests.cs index a565381..3637cbf 100644 --- a/tests/EntityFrameworkCore.Projectables.Tests/Extensions/TypeExtensionTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Tests/Extensions/TypeExtensionTests.cs @@ -32,6 +32,17 @@ public override void VirtualMethod(int arg1) { } public override void GenericVirtualMethod(TArg arg1) { } } + interface IStringId + { + string Id { get; } + } + + class ItemWithExplicitInterfaceImplementation : IStringId + { + public int Id { get; set; } + string IStringId.Id => Id.ToString(); + } + [Fact] public void GetNestedTypePath_OuterType_Returns1Entry() { @@ -120,5 +131,41 @@ public void GetOverridingMethod_DerivedTypeGenericVirtualMethod_FindsOverridingM Assert.Equal(derivedMethod, resolvedMethod); } + + [Fact] + public void GetImplementingProperty_ExplicitInterfaceImplementation_ReturnsConcreteImplementation() + { + // This test verifies that when a class explicitly implements an interface property + // (e.g., string IStringId.Id => Id.ToString();), GetImplementingProperty doesn't throw + // an InvalidOperationException + var interfaceType = typeof(IStringId); + var interfaceProperty = interfaceType.GetProperty("Id")!; + var concreteType = typeof(ItemWithExplicitInterfaceImplementation); + + // This should not throw InvalidOperationException + var result = concreteType.GetImplementingProperty(interfaceProperty); + + // The result should be the explicit interface implementation property + Assert.NotNull(result); + Assert.NotEqual(interfaceProperty, result); + Assert.Contains("IStringId.Id", result.Name); + } + + [Fact] + public void GetConcreteProperty_ExplicitInterfaceImplementation_ReturnsConcreteImplementation() + { + // This test verifies the same scenario but through GetConcreteProperty which is the entry point + var interfaceType = typeof(IStringId); + var interfaceProperty = interfaceType.GetProperty("Id")!; + var concreteType = typeof(ItemWithExplicitInterfaceImplementation); + + // This should not throw InvalidOperationException + var result = concreteType.GetConcreteProperty(interfaceProperty); + + // The result should be the explicit interface implementation property + Assert.NotNull(result); + Assert.NotEqual(interfaceProperty, result); + Assert.Contains("IStringId.Id", result.Name); + } } }