diff --git a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs index 94b63b2..6edbee0 100644 --- a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs +++ b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs @@ -1,10 +1,10 @@ namespace EntityFrameworkCore.Projectables { /// - /// Declares this property or method to be Projectable. + /// Declares this property, method or constructor to be Projectable. /// A companion Expression tree will be generated /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, Inherited = true, AllowMultiple = false)] public sealed class ProjectableAttribute : Attribute { /// diff --git a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md index 253db78..249da29 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md +++ b/src/EntityFrameworkCore.Projectables.Generator/AnalyzerReleases.Shipped.md @@ -8,6 +8,7 @@ EFP0003 | Design | Warning | Unsupported statement in block-bodied method EFP0004 | Design | Error | Statement with side effects in block-bodied method EFP0005 | Design | Warning | Potential side effect in block-bodied method EFP0006 | Design | Error | Method or property should expose a body definition (block or expression) +EFP0007 | Design | Error | Target class is missing a parameterless constructor ### Changed Rules diff --git a/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs b/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs new file mode 100644 index 0000000..15618b0 --- /dev/null +++ b/src/EntityFrameworkCore.Projectables.Generator/ConstructorBodyConverter.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace EntityFrameworkCore.Projectables.Generator +{ + /// + /// Converts constructor body statements into a dictionary of property-name → expression + /// pairs that are used to build a member-init expression for EF Core projections. + /// Supports simple assignments, local variable declarations, and if/else statements. + /// Previously-assigned properties (including those from a delegated base/this ctor) are + /// inlined when referenced in subsequent assignments. + /// + public class ConstructorBodyConverter + { + private readonly SourceProductionContext _context; + private readonly Func _rewrite; + private readonly Dictionary _paramSubstitutions; + private readonly Dictionary _localVariables = new(); + + /// Creates a converter for the main constructor body. + public ConstructorBodyConverter( + SourceProductionContext context, + ExpressionSyntaxRewriter expressionRewriter) + { + _context = context; + _rewrite = expr => (ExpressionSyntax)expressionRewriter.Visit(expr); + _paramSubstitutions = new Dictionary(); + } + + /// Creates a converter for a delegated (base/this) constructor body. + public ConstructorBodyConverter( + SourceProductionContext context, + Dictionary paramSubstitutions) + { + _context = context; + _rewrite = expr => expr; + _paramSubstitutions = paramSubstitutions; + } + + public Dictionary? TryConvertBody( + IEnumerable statements, + string memberName, + IReadOnlyDictionary? initialContext = null) + { + var assignments = new Dictionary(); + if (!TryProcessBlock(statements, assignments, memberName, outerContext: initialContext)) + { + return null; + } + + return assignments; + } + + private bool TryProcessBlock( + IEnumerable statements, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? outerContext) + { + foreach (var statement in statements) + { + // Everything accumulated so far (from outer scope + this block) is visible. + var visible = BuildVisible(outerContext, assignments); + if (!TryProcessStatement(statement, assignments, memberName, visible)) + { + return false; + } + } + return true; + } + + private bool TryProcessStatement( + StatementSyntax statement, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? visibleContext) + { + switch (statement) + { + case LocalDeclarationStatementSyntax localDecl: + return TryProcessLocalDeclaration(localDecl, memberName, visibleContext); + + case ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax assignment } + when assignment.IsKind(SyntaxKind.SimpleAssignmentExpression): + return TryProcessAssignment(assignment, assignments, memberName, visibleContext); + + case IfStatementSyntax ifStmt: + return TryProcessIfStatement(ifStmt, assignments, memberName, visibleContext); + + case BlockSyntax block: + return TryProcessBlock(block.Statements, assignments, memberName, visibleContext); + + default: + ReportUnsupported(statement, memberName, + $"Statement type '{statement.GetType().Name}' is not supported in a [Projectable] constructor body. " + + "Only assignments, local variable declarations, and if/else statements are supported."); + return false; + } + } + + private bool TryProcessLocalDeclaration( + LocalDeclarationStatementSyntax localDecl, + string memberName, + IReadOnlyDictionary? visibleContext) + { + foreach (var variable in localDecl.Declaration.Variables) + { + if (variable.Initializer == null) + { + ReportUnsupported(localDecl, memberName, "Local variables must have an initializer"); + return false; + } + + var rewritten = _rewrite(variable.Initializer.Value); + rewritten = ApplySubstitutions(rewritten, visibleContext); + _localVariables[variable.Identifier.Text] = rewritten; + } + return true; + } + + private bool TryProcessAssignment( + AssignmentExpressionSyntax assignment, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? visibleContext) + { + var targetMember = GetTargetMember(assignment.Left); + if (targetMember is null) + { + ReportUnsupported(assignment, memberName, + $"Unsupported assignment target '{assignment.Left}'. " + + "Only 'PropertyName = ...' or 'this.PropertyName = ...' are supported."); + return false; + } + + var rewritten = _rewrite(assignment.Right); + rewritten = ApplySubstitutions(rewritten, visibleContext); + assignments[targetMember.Identifier.Text] = rewritten; + return true; + } + + private bool TryProcessIfStatement( + IfStatementSyntax ifStmt, + Dictionary assignments, + string memberName, + IReadOnlyDictionary? visibleContext) + { + var condition = _rewrite(ifStmt.Condition); + condition = ApplySubstitutions(condition, visibleContext); + + // Each branch starts empty but can see the pre-if accumulated props (visibleContext). + var thenAssignments = new Dictionary(); + if (!TryProcessBlock(GetStatements(ifStmt.Statement), thenAssignments, memberName, visibleContext)) + { + return false; + } + + var elseAssignments = new Dictionary(); + if (ifStmt.Else != null) + { + if (!TryProcessBlock(GetStatements(ifStmt.Else.Statement), elseAssignments, memberName, visibleContext)) + { + return false; + } + } + + foreach (var thenKvp in thenAssignments) + { + var prop = thenKvp.Key; + var thenExpr = thenKvp.Value; + + ExpressionSyntax elseExpr; + if (elseAssignments.TryGetValue(prop, out var elseVal)) + { + elseExpr = elseVal; + } + else if (assignments.TryGetValue(prop, out var existing)) + { + elseExpr = existing; + } + else + { + elseExpr = DefaultLiteral(); + } + + assignments[prop] = SyntaxFactory.ConditionalExpression(condition, thenExpr, elseExpr); + } + + foreach (var elseKvp in elseAssignments) + { + var prop = elseKvp.Key; + var elseExpr = elseKvp.Value; + + if (thenAssignments.ContainsKey(prop)) + { + continue; + } + + ExpressionSyntax thenExpr; + if (assignments.TryGetValue(prop, out var existing)) + { + thenExpr = existing; + } + else + { + thenExpr = DefaultLiteral(); + } + + assignments[prop] = SyntaxFactory.ConditionalExpression(condition, thenExpr, elseExpr); + } + + return true; + } + + private static IEnumerable GetStatements(StatementSyntax statement) => + statement is BlockSyntax block + ? block.Statements + : new StatementSyntax[] { statement }; + + private static IdentifierNameSyntax? GetTargetMember(ExpressionSyntax left) => + left switch + { + MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax, Name: IdentifierNameSyntax name } => name, + IdentifierNameSyntax ident => ident, + _ => null + }; + + /// + /// Merges outer (parent scope) and local (current block) accumulated dictionaries + /// into a single read-only view for use as a substitution context. + /// Local entries take priority over outer entries. + /// Returns null when both are empty (avoids unnecessary allocations). + /// + private static IReadOnlyDictionary? BuildVisible( + IReadOnlyDictionary? outer, + Dictionary local) + { + bool outerEmpty = outer == null || outer.Count == 0; + var localEmpty = local.Count == 0; + + if (outerEmpty && localEmpty) + { + return null; + } + + if (outerEmpty) + { + return local; + } + + if (localEmpty) + { + return outer; + } + + var merged = new Dictionary(); + foreach (var kvp in outer!) + { + merged[kvp.Key] = kvp.Value; + } + + foreach (var kvp in local) + { + merged[kvp.Key] = kvp.Value; + } + + return merged; + } + + private ExpressionSyntax ApplySubstitutions( + ExpressionSyntax expr, + IReadOnlyDictionary? visibleContext) + { + if (_paramSubstitutions.Count > 0) + { + expr = ParameterSubstitutor.Substitute(expr, _paramSubstitutions); + } + + if (_localVariables.Count > 0) + { + expr = LocalVariableSubstitutor.Substitute(expr, _localVariables); + } + + if (visibleContext != null && visibleContext.Count > 0) + { + expr = AssignedPropertySubstitutor.Substitute(expr, visibleContext); + } + + return expr; + } + + private static ExpressionSyntax DefaultLiteral() => + SyntaxFactory.LiteralExpression( + SyntaxKind.DefaultLiteralExpression, + SyntaxFactory.Token(SyntaxKind.DefaultKeyword)); + + private void ReportUnsupported(SyntaxNode node, string memberName, string reason) + { + _context.ReportDiagnostic(Diagnostic.Create( + Diagnostics.UnsupportedStatementInBlockBody, + node.GetLocation(), + memberName, + reason)); + } + + /// + /// Replaces parameter-name identifier references with call-site argument expressions + /// (used when inlining a delegated base/this constructor body). + /// + internal sealed class ParameterSubstitutor : CSharpSyntaxRewriter + { + private readonly Dictionary _map; + private ParameterSubstitutor(Dictionary map) => _map = map; + + public static ExpressionSyntax Substitute(ExpressionSyntax expr, Dictionary map) + => (ExpressionSyntax)new ParameterSubstitutor(map).Visit(expr); + + public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) + => _map.TryGetValue(node.Identifier.Text, out var replacement) + ? replacement.WithTriviaFrom(node) + : base.VisitIdentifierName(node); + } + + /// + /// Replaces local-variable identifier references with their inlined (parenthesised) + /// initializer expressions. + /// + private sealed class LocalVariableSubstitutor : CSharpSyntaxRewriter + { + private readonly Dictionary _locals; + private LocalVariableSubstitutor(Dictionary locals) => _locals = locals; + + public static ExpressionSyntax Substitute(ExpressionSyntax expr, Dictionary locals) + => (ExpressionSyntax)new LocalVariableSubstitutor(locals).Visit(expr); + + public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) + => _locals.TryGetValue(node.Identifier.Text, out var replacement) + ? SyntaxFactory.ParenthesizedExpression(replacement.WithoutTrivia()).WithTriviaFrom(node) + : base.VisitIdentifierName(node); + } + + /// + /// Replaces references to previously-assigned properties with the expression that was + /// assigned to them, so that EF Core sees a fully-inlined projection. + /// + /// Handles two syntactic forms: + /// + /// @this.PropName — produced by for + /// instance-member references in the main constructor body. + /// Bare PropName identifier — appears in delegated (base/this) constructor + /// bodies where the identity rewriter is used. + /// + /// + /// + private sealed class AssignedPropertySubstitutor : CSharpSyntaxRewriter + { + private readonly IReadOnlyDictionary _accumulated; + + private AssignedPropertySubstitutor(IReadOnlyDictionary accumulated) + => _accumulated = accumulated; + + public static ExpressionSyntax Substitute( + ExpressionSyntax expr, + IReadOnlyDictionary accumulated) + => (ExpressionSyntax)new AssignedPropertySubstitutor(accumulated).Visit(expr); + + // Catches @this.PropName → inline the accumulated expression for PropName. + public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + { + if (node.Expression is IdentifierNameSyntax thisRef && + (thisRef.Identifier.Text == "@this" || thisRef.Identifier.ValueText == "this") && + node.Name is IdentifierNameSyntax propName && + _accumulated.TryGetValue(propName.Identifier.Text, out var replacement)) + { + return SyntaxFactory.ParenthesizedExpression(replacement.WithoutTrivia()) + .WithTriviaFrom(node); + } + + return base.VisitMemberAccessExpression(node); + } + + // Catches bare PropName → inline accumulated expression (delegated ctor case). + // Params and locals have already been substituted before this runs, so any remaining + // bare identifier that matches an accumulated property key is a property reference. + public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) + { + // Do not substitute special identifiers (@this, type keywords, etc.) + var text = node.Identifier.Text; + if (text.StartsWith("@") || text == "default" || text == "null" || text == "true" || text == "false") + { + return base.VisitIdentifierName(node); + } + + if (_accumulated.TryGetValue(text, out var replacement)) + { + return SyntaxFactory.ParenthesizedExpression(replacement.WithoutTrivia()) + .WithTriviaFrom(node); + } + + return base.VisitIdentifierName(node); + } + } + } +} diff --git a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs index 70e2964..5e88014 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/Diagnostics.cs @@ -52,5 +52,13 @@ public static class Diagnostics DiagnosticSeverity.Error, isEnabledByDefault: true); + public static readonly DiagnosticDescriptor MissingParameterlessConstructor = new DiagnosticDescriptor( + id: "EFP0007", + title: "Target class is missing a parameterless constructor", + messageFormat: "Class '{0}' must have a parameterless constructor to be used with a [Projectable] constructor. The generated projection uses 'new {0}() {{ ... }}' (object-initializer syntax), which requires a publicly accessible parameterless constructor.", + category: "Design", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + } } diff --git a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs index 3037fb1..bda392f 100644 --- a/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs +++ b/src/EntityFrameworkCore.Projectables.Generator/ProjectableInterpreter.cs @@ -188,19 +188,24 @@ x is IPropertySymbol xProperty && ? memberSymbol.ContainingType.ContainingType : memberSymbol.ContainingType; + var methodSymbol = memberSymbol as IMethodSymbol; + + // Sanitize constructor name (.ctor / .cctor are not valid C# identifiers, use _ctor) + var memberName = methodSymbol?.MethodKind is MethodKind.Constructor or MethodKind.StaticConstructor + ? "_ctor" + : memberSymbol.Name; + var descriptor = new ProjectableDescriptor { UsingDirectives = member.SyntaxTree.GetRoot().DescendantNodes().OfType(), ClassName = classForNaming.Name, ClassNamespace = classForNaming.ContainingNamespace.IsGlobalNamespace ? null : classForNaming.ContainingNamespace.ToDisplayString(), - MemberName = memberSymbol.Name, + MemberName = memberName, 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) @@ -288,7 +293,7 @@ x is IPropertySymbol xProperty && ) ); } - else if (!member.Modifiers.Any(SyntaxKind.StaticKeyword)) + else if (!member.Modifiers.Any(SyntaxKind.StaticKeyword) && member is not ConstructorDeclarationSyntax) { descriptor.ParametersList = descriptor.ParametersList.AddParameters( SyntaxFactory.Parameter( @@ -452,6 +457,116 @@ x is IPropertySymbol xProperty && ? bodyExpression : (ExpressionSyntax)expressionSyntaxRewriter.Visit(bodyExpression); } + // Projectable constructors + else if (memberBody is ConstructorDeclarationSyntax constructorDeclarationSyntax) + { + var containingType = memberSymbol.ContainingType; + var fullTypeName = containingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + descriptor.ReturnTypeName = fullTypeName; + + // Add the constructor's own parameters to the lambda parameter list + foreach (var additionalParameter in ((ParameterListSyntax)declarationSyntaxRewriter.Visit(constructorDeclarationSyntax.ParameterList)).Parameters) + { + descriptor.ParametersList = descriptor.ParametersList.AddParameters(additionalParameter); + } + + // Accumulated property-name → expression map (later converted to member-init) + var accumulatedAssignments = new Dictionary(); + + // 1. Process base/this initializer: propagate property assignments from the + // delegated constructor so callers don't have to duplicate them in the body. + if (constructorDeclarationSyntax.Initializer is { } initializer) + { + var initializerSymbol = semanticModel.GetSymbolInfo(initializer).Symbol as IMethodSymbol; + if (initializerSymbol is not null) + { + var delegatedAssignments = CollectDelegatedConstructorAssignments( + initializerSymbol, + initializer.ArgumentList.Arguments, + expressionSyntaxRewriter, + context, + memberSymbol.Name); + + if (delegatedAssignments is null) + { + return null; + } + + foreach (var kvp in delegatedAssignments) + { + accumulatedAssignments[kvp.Key] = kvp.Value; + } + } + } + + // 2. Process this constructor's body (supports assignments, locals, if/else). + // Pass the already-accumulated base/this initializer assignments as the initial + // visible context so that references to those properties are correctly inlined. + if (constructorDeclarationSyntax.Body is { } body) + { + var bodyConverter = new ConstructorBodyConverter(context, expressionSyntaxRewriter); + IReadOnlyDictionary? initialCtx = + accumulatedAssignments.Count > 0 ? accumulatedAssignments : null; + var bodyAssignments = bodyConverter.TryConvertBody(body.Statements, memberSymbol.Name, initialCtx); + + if (bodyAssignments is null) + { + return null; + } + + // Body assignments override anything set by the base/this initializer + foreach (var kvp in bodyAssignments) + { + accumulatedAssignments[kvp.Key] = kvp.Value; + } + } + + if (accumulatedAssignments.Count == 0) + { + var diag = Diagnostic.Create(Diagnostics.RequiresBodyDefinition, + constructorDeclarationSyntax.GetLocation(), memberSymbol.Name); + context.ReportDiagnostic(diag); + return null; + } + + // Verify the containing type has a parameterless (instance) constructor. + // The generated projection is: new T() { Prop = ... }, which requires one. + // INamedTypeSymbol.Constructors covers all partial declarations and also + // the implicit parameterless constructor that the compiler synthesizes when + // no constructors are explicitly defined. + var hasParameterlessConstructor = containingType.Constructors + .Any(c => !c.IsStatic && c.Parameters.IsEmpty); + + if (!hasParameterlessConstructor) + { + context.ReportDiagnostic(Diagnostic.Create( + Diagnostics.MissingParameterlessConstructor, + constructorDeclarationSyntax.GetLocation(), + containingType.Name)); + return null; + } + + var initExpressions = accumulatedAssignments + .Select(kvp => (ExpressionSyntax)SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.IdentifierName(kvp.Key), + kvp.Value)) + .ToList(); + + var memberInit = SyntaxFactory.InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SyntaxFactory.SeparatedList(initExpressions)); + + // Use a parameterless constructor + object initializer so EF Core only + // projects columns explicitly listed in the member-init bindings. + descriptor.ExpressionBody = SyntaxFactory.ObjectCreationExpression( + SyntaxFactory.Token(SyntaxKind.NewKeyword).WithTrailingTrivia(SyntaxFactory.Space), + SyntaxFactory.ParseTypeName(fullTypeName), + SyntaxFactory.ArgumentList(), + memberInit + ); + } else { return null; @@ -460,6 +575,123 @@ x is IPropertySymbol xProperty && return descriptor; } + /// + /// Collects the property-assignment expressions that the delegated constructor (base/this) + /// would perform, substituting its parameters with the actual call-site argument expressions. + /// Supports if/else logic inside the delegated constructor body, and follows the chain of + /// base/this initializers recursively. + /// Returns null when an unsupported statement is encountered (diagnostics reported). + /// + private static Dictionary? CollectDelegatedConstructorAssignments( + IMethodSymbol delegatedCtor, + SeparatedSyntaxList callerArgs, + ExpressionSyntaxRewriter expressionSyntaxRewriter, + SourceProductionContext context, + string memberName, + bool argsAlreadyRewritten = false) + { + // Only process constructors whose source is available in this compilation + var syntax = delegatedCtor.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType() + .FirstOrDefault(); + + if (syntax is null) + { + return new Dictionary(); + } + + // Build a mapping: delegated-param-name → caller argument expression. + // First-level args come from the original syntax tree and must be visited by the + // ExpressionSyntaxRewriter. Recursive-level args are already-substituted detached + // nodes and must NOT be visited (doing so throws "node not in syntax tree"). + var paramToArg = new Dictionary(); + for (var i = 0; i < callerArgs.Count && i < delegatedCtor.Parameters.Length; i++) + { + var paramName = delegatedCtor.Parameters[i].Name; + var argExpr = argsAlreadyRewritten + ? callerArgs[i].Expression + : (ExpressionSyntax)expressionSyntaxRewriter.Visit(callerArgs[i].Expression); + paramToArg[paramName] = argExpr; + } + + // The accumulated assignments start from the delegated ctor's own initializer (if any), + // so that base/this chains are followed recursively. + var accumulated = new Dictionary(); + + if (syntax.Initializer is { } delegatedInitializer) + { + // The delegated ctor's initializer is part of the original syntax tree, + // so we can safely use the semantic model to resolve its symbol. + var semanticModel = expressionSyntaxRewriter.GetSemanticModel(); + var delegatedInitializerSymbol = + semanticModel.GetSymbolInfo(delegatedInitializer).Symbol as IMethodSymbol; + + if (delegatedInitializerSymbol is not null) + { + // Substitute the delegated ctor's initializer arguments using our paramToArg map, + // so that e.g. `: base(id)` becomes `: base()`. + var substitutedInitArgs = SubstituteArguments( + delegatedInitializer.ArgumentList.Arguments, paramToArg); + + var chainedAssignments = CollectDelegatedConstructorAssignments( + delegatedInitializerSymbol, + substitutedInitArgs, + expressionSyntaxRewriter, + context, + memberName, + argsAlreadyRewritten: true); // args are now detached substituted nodes + + if (chainedAssignments is null) + return null; + + foreach (var kvp in chainedAssignments) + accumulated[kvp.Key] = kvp.Value; + } + } + + if (syntax.Body is null) + return accumulated; + + // Use ConstructorBodyConverter (identity rewriter + param substitutions) so that + // if/else, local variables and simple assignments in the delegated ctor are all handled. + // Pass the already-accumulated chained assignments as the initial visible context. + IReadOnlyDictionary? initialCtx = + accumulated.Count > 0 ? accumulated : null; + var converter = new ConstructorBodyConverter(context, paramToArg); + var bodyAssignments = converter.TryConvertBody(syntax.Body.Statements, memberName, initialCtx); + + if (bodyAssignments is null) + return null; + + foreach (var kvp in bodyAssignments) + accumulated[kvp.Key] = kvp.Value; + + return accumulated; + } + + /// + /// Substitutes identifiers in using the + /// mapping. This is used to forward the outer caller's arguments through a chain of + /// base/this initializer calls. + /// + private static SeparatedSyntaxList SubstituteArguments( + SeparatedSyntaxList args, + Dictionary paramToArg) + { + if (paramToArg.Count == 0) + return args; + + var result = new List(); + foreach (var arg in args) + { + var substituted = ConstructorBodyConverter.ParameterSubstitutor.Substitute( + arg.Expression, paramToArg); + result.Add(arg.WithExpression(substituted)); + } + return SyntaxFactory.SeparatedList(result); + } + private static TypeConstraintSyntax MakeTypeConstraint(string constraint) => SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName(constraint)); } } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index 8f9f4a1..6d815e2 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -15,6 +15,7 @@ public sealed class ProjectableExpressionReplacer : ExpressionVisitor private readonly IProjectionExpressionResolver _resolver; private readonly ExpressionArgumentReplacer _expressionArgumentReplacer = new(); private readonly Dictionary _projectableMemberCache = new(); + private readonly HashSet _expandingConstructors = new(); private IQueryProvider? _currentQueryProvider; private bool _disableRootRewrite = false; private readonly bool _trackingByDefault; @@ -203,6 +204,39 @@ protected override Expression VisitMethodCall(MethodCallExpression node) return base.VisitMethodCall(node); } + protected override Expression VisitNew(NewExpression node) + { + var constructor = node.Constructor; + if (constructor is not null && + !_expandingConstructors.Contains(constructor) && + TryGetReflectedExpression(constructor, out var reflectedExpression)) + { + _expandingConstructors.Add(constructor); + try + { + for (var parameterIndex = 0; parameterIndex < reflectedExpression.Parameters.Count; parameterIndex++) + { + var parameterExpression = reflectedExpression.Parameters[parameterIndex]; + if (parameterIndex < node.Arguments.Count) + { + _expressionArgumentReplacer.ParameterArgumentMapping.Add(parameterExpression, node.Arguments[parameterIndex]); + } + } + + var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body); + _expressionArgumentReplacer.ParameterArgumentMapping.Clear(); + + return base.Visit(updatedBody); + } + finally + { + _expandingConstructors.Remove(constructor); + } + } + + return base.VisitNew(node); + } + protected override Expression VisitMember(MemberExpression node) { // Evaluate captured variables in closures that contain EF queries to inline them into the main query diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs index b6ded59..7e26d69 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs @@ -77,6 +77,7 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo // Use the same format as Roslyn's SymbolDisplayFormat.FullyQualifiedFormat // which uses C# keywords for primitive types (int, string, etc.) string[]? parameterTypeNames = null; + string memberLookupName = projectableMemberInfo.Name; if (projectableMemberInfo is MethodInfo method) { // For generic methods, use the generic definition to get parameter types @@ -87,8 +88,16 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo .Select(p => GetFullTypeName(p.ParameterType)) .ToArray(); } + else if (projectableMemberInfo is ConstructorInfo ctor) + { + // Constructors are stored under the synthetic name "_ctor" + memberLookupName = "_ctor"; + parameterTypeNames = ctor.GetParameters() + .Select(p => GetFullTypeName(p.ParameterType)) + .ToArray(); + } - var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name, parameterTypeNames); + var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), memberLookupName, parameterTypeNames); var expressionFactoryType = declaringType.Assembly.GetType(generatedContainingTypeName); diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt new file mode 100644 index 0000000..6a9d698 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingBasePropertyInDerivedBody.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName] AS [Code], N'[' + COALESCE([p].[FirstName], N'') + N']' AS [Label] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingPreviouslyAssignedProperty.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt new file mode 100644 index 0000000..2752cb7 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorReferencingStaticConstMember.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' - ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt new file mode 100644 index 0000000..2f9967b --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet10_0.verified.txt @@ -0,0 +1,5 @@ +SELECT CASE + WHEN [p].[Id] < 0 THEN 0 + ELSE [p].[Id] +END AS [Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt new file mode 100644 index 0000000..2f9967b --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.DotNet9_0.verified.txt @@ -0,0 +1,5 @@ +SELECT CASE + WHEN [p].[Id] < 0 THEN 0 + ELSE [p].[Id] +END AS [Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt new file mode 100644 index 0000000..2f9967b --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerAndIfElse.verified.txt @@ -0,0 +1,5 @@ +SELECT CASE + WHEN [p].[Id] < 0 THEN 0 + ELSE [p].[Id] +END AS [Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt new file mode 100644 index 0000000..0c0f115 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT UPPER([p].[LastName]) AS [Code], [p].[FirstName] AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt new file mode 100644 index 0000000..0c0f115 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT UPPER([p].[LastName]) AS [Code], [p].[FirstName] AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt new file mode 100644 index 0000000..0c0f115 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithBaseInitializerExpression.verified.txt @@ -0,0 +1,2 @@ +SELECT UPPER([p].[LastName]) AS [Code], [p].[FirstName] AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt new file mode 100644 index 0000000..68ebfbf --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet10_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Id], CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt new file mode 100644 index 0000000..68ebfbf --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.DotNet9_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Id], CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt new file mode 100644 index 0000000..68ebfbf --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithIfElseLogic.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Id], CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ConstructorWithLocalVariable.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_DerivedDtoWithBaseConstructor.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_EntityInstanceToDto.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithThreeArgs.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt new file mode 100644 index 0000000..dee2833 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt new file mode 100644 index 0000000..dee2833 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt new file mode 100644 index 0000000..8fc80f2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_OverloadedConstructor_WithTwoArgs.verified.txt @@ -0,0 +1,2 @@ +SELECT COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt new file mode 100644 index 0000000..fa0395a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ScalarFieldsToDto.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt new file mode 100644 index 0000000..6004f0a --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_ChainedThisAndBase.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N'-' + COALESCE([p].[LastName], N'') AS [Name] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt new file mode 100644 index 0000000..8464fb5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_SimpleDelegate.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], N'' AS [LastName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet8_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt new file mode 100644 index 0000000..cccd5bb --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithBodyAfterDelegation.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[FirstName], [p].[LastName], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet10_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet8_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.DotNet9_0.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt new file mode 100644 index 0000000..c6a5c18 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_ThisOverload_WithIfElseInDelegated.verified.txt @@ -0,0 +1,5 @@ +SELECT [p].[Score], N'Grade:' + CASE + WHEN [p].[Score] >= 90 THEN N'A' + ELSE N'B' +END AS [Grade] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet10_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.DotNet9_0.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt new file mode 100644 index 0000000..a8f3f2f --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.Select_UnassignedPropertyNotInQuery.verified.txt @@ -0,0 +1,2 @@ +SELECT [p].[Id], COALESCE([p].[FirstName], N'') + N' ' + COALESCE([p].[LastName], N'') AS [FullName] +FROM [PersonEntity] AS [p] \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs new file mode 100644 index 0000000..af6d9ea --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ProjectableConstructorTests.cs @@ -0,0 +1,567 @@ +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 ProjectableConstructorTests + { + public class PersonEntity + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public int Score { get; set; } + } + + /// DTO built from scalar entity fields. + public class PersonSummaryDto + { + public int Id { get; set; } + public string FullName { get; set; } + + public PersonSummaryDto() { } // required: EF Core uses the parameterless ctor + + [Projectable] + public PersonSummaryDto(int id, string firstName, string lastName) + { + Id = id; + FullName = firstName + " " + lastName; + } + } + + /// DTO built by passing the whole entity instance as the constructor argument. + public class PersonFromEntityDto + { + public int Id { get; set; } + public string FullName { get; set; } + + public PersonFromEntityDto() { } // required: EF Core uses the parameterless ctor + + [Projectable] + public PersonFromEntityDto(PersonEntity entity) + { + Id = entity.Id; + FullName = entity.FirstName + " " + entity.LastName; + } + } + + public class BaseDto + { + public int Id { get; set; } + + public BaseDto() { } // required + public BaseDto(int id) { Id = id; } + } + + public class DerivedDto : BaseDto + { + public string FullName { get; set; } + + public DerivedDto() { } // required: EF Core uses the parameterless ctor + + /// + /// Id is automatically included from : base(id) — no need to repeat it here. + /// + [Projectable] + public DerivedDto(int id, string firstName, string lastName) : base(id) + { + FullName = firstName + " " + lastName; + } + } + + public class PersonOverloadedDto + { + public int Id { get; set; } + public string FullName { get; set; } + + public PersonOverloadedDto() { } // required: EF Core uses the parameterless ctor + + [Projectable] + public PersonOverloadedDto(int id, string firstName, string lastName) + { + Id = id; + FullName = firstName + " " + lastName; + } + + [Projectable] + public PersonOverloadedDto(string firstName, string lastName) + { + // Id deliberately not set → should not appear as a DB column in the query + FullName = firstName + " " + lastName; + } + } + + // ── Partial / unmapped property ─────────────────────────────────────────── + + /// + /// DTO with a Nickname property that is intentionally NOT assigned + /// in the [Projectable] constructor body. + /// The generated SQL must NOT include a column for Nickname. + /// + public class PersonPartialDto + { + public int Id { get; set; } + public string FullName { get; set; } + public string Nickname { get; set; } // intentionally unmapped in the constructor + + public PersonPartialDto() { } // required + + [Projectable] + public PersonPartialDto(int id, string firstName, string lastName) + { + Id = id; + FullName = firstName + " " + lastName; + // Nickname is intentionally NOT assigned here + } + } + + /// DTO with if/else logic in the constructor body. + public class PersonGradeDto + { + public int Id { get; set; } + public string Grade { get; set; } + + public PersonGradeDto() { } + + [Projectable] + public PersonGradeDto(int id, int score) + { + Id = id; + if (score >= 90) + { + Grade = "A"; + } + else + { + Grade = "B"; + } + } + } + + /// DTO using a local variable in the constructor body. + public class PersonLocalVarDto + { + public string FullName { get; set; } + + public PersonLocalVarDto() { } + + [Projectable] + public PersonLocalVarDto(string first, string last) + { + var full = first + " " + last; + FullName = full; + } + } + + public class PersonBaseWithExprDto + { + public string Code { get; set; } + + public PersonBaseWithExprDto() { } + public PersonBaseWithExprDto(string code) { Code = code; } + } + + public class PersonDerivedWithExprDto : PersonBaseWithExprDto + { + public string Name { get; set; } + + public PersonDerivedWithExprDto() { } + + [Projectable] + public PersonDerivedWithExprDto(string name, string rawCode) : base(rawCode.ToUpper()) + { + Name = name; + } + } + + public class PersonBaseWithLogicDto + { + public int Id { get; set; } + + public PersonBaseWithLogicDto() { } + public PersonBaseWithLogicDto(int id) + { + if (id < 0) + { + Id = 0; + } + else + { + Id = id; + } + } + } + + public class PersonDerivedWithBaseLogicDto : PersonBaseWithLogicDto + { + public string FullName { get; set; } + + public PersonDerivedWithBaseLogicDto() { } + + [Projectable] + public PersonDerivedWithBaseLogicDto(int id, string firstName, string lastName) : base(id) + { + FullName = firstName + " " + lastName; + } + } + + // ── Referencing previously-assigned property ────────────────────────────── + + public class PersonWithCompositeDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonWithCompositeDto() { } + + [Projectable] + public PersonWithCompositeDto(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + FullName = FirstName + " " + LastName; // references previously-assigned props + } + } + + // ── Referencing base property in derived body ───────────────────────────── + + public class PersonBaseCodeDto + { + public string Code { get; set; } + public PersonBaseCodeDto() { } + public PersonBaseCodeDto(string code) { Code = code; } + } + + public class PersonDerivedLabelDto : PersonBaseCodeDto + { + public string Label { get; set; } + + public PersonDerivedLabelDto() { } + + [Projectable] + public PersonDerivedLabelDto(string code) : base(code) + { + Label = "[" + Code + "]"; // Code was set by base ctor → should inline it + } + } + + // ── Static / const member ───────────────────────────────────────────────── + + public class PersonWithConstSeparatorDto + { + internal const string Separator = " - "; + + public string FullName { get; set; } + + public PersonWithConstSeparatorDto() { } + + [Projectable] + public PersonWithConstSeparatorDto(string firstName, string lastName) + { + FullName = firstName + Separator + lastName; + } + } + + // ── this() overload – simple delegation ─────────────────────────────────── + + public class PersonThisSimpleDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonThisSimpleDto() { } + + public PersonThisSimpleDto(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + } + + /// Delegates to the 2-arg ctor using a split on the full name. + [Projectable] + public PersonThisSimpleDto(string fullName) : this(fullName, "") + { + } + } + + // ── this() overload – with additional body after delegation ─────────────── + + public class PersonThisBodyAfterDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonThisBodyAfterDto() { } + + public PersonThisBodyAfterDto(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonThisBodyAfterDto(string firstName, string lastName, bool upper) : this(firstName, lastName) + { + FullName = upper ? (FirstName + " " + LastName).ToUpper() : FirstName + " " + LastName; + } + } + + // ── this() overload – if/else logic in the delegated constructor ────────── + + public class PersonThisIfElseDto + { + public string Grade { get; set; } + public int Score { get; set; } + + public PersonThisIfElseDto() { } + + public PersonThisIfElseDto(int score) + { + Score = score; + if (score >= 90) + Grade = "A"; + else + Grade = "B"; + } + + [Projectable] + public PersonThisIfElseDto(int score, string prefix) : this(score) + { + Grade = prefix + Grade; + } + } + + // ── this() → base() chain ───────────────────────────────────────────────── + + public class PersonChainBase + { + public int Id { get; set; } + public PersonChainBase() { } + public PersonChainBase(int id) { Id = id; } + } + + public class PersonChainChild : PersonChainBase + { + public string Name { get; set; } + + public PersonChainChild() { } + + public PersonChainChild(int id, string name) : base(id) + { + Name = name; + } + + [Projectable] + public PersonChainChild(int id, string name, string suffix) : this(id, name) + { + Name = Name + suffix; + } + } + + [Fact] + public Task Select_ScalarFieldsToDto() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonSummaryDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_EntityInstanceToDto() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonFromEntityDto(p)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_DerivedDtoWithBaseConstructor() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new DerivedDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_OverloadedConstructor_WithThreeArgs() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonOverloadedDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_OverloadedConstructor_WithTwoArgs() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonOverloadedDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + /// + /// Verifies that a property not assigned in the [Projectable] constructor body + /// does NOT appear as a column in the generated SQL query. + /// + [Fact] + public Task Select_UnassignedPropertyNotInQuery() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonPartialDto(p.Id, p.FirstName, p.LastName)); + + var sql = query.ToQueryString(); + + // Nickname is not assigned in the constructor → must not appear in SQL + Assert.DoesNotContain("Nickname", sql, System.StringComparison.OrdinalIgnoreCase); + + return Verifier.Verify(sql); + } + + [Fact] + public Task Select_ConstructorWithIfElseLogic() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonGradeDto(p.Id, p.Score)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorWithLocalVariable() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonLocalVarDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorWithBaseInitializerExpression() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonDerivedWithExprDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorWithBaseInitializerAndIfElse() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonDerivedWithBaseLogicDto(p.Id, p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorReferencingPreviouslyAssignedProperty() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonWithCompositeDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorReferencingBasePropertyInDerivedBody() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonDerivedLabelDto(p.FirstName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ConstructorReferencingStaticConstMember() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonWithConstSeparatorDto(p.FirstName, p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_SimpleDelegate() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonThisSimpleDto(p.FirstName)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_WithBodyAfterDelegation() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonThisBodyAfterDto(p.FirstName, p.LastName, false)); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_WithIfElseInDelegated() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonThisIfElseDto(p.Score, "Grade:")); + + return Verifier.Verify(query.ToQueryString()); + } + + [Fact] + public Task Select_ThisOverload_ChainedThisAndBase() + { + using var dbContext = new SampleDbContext(); + + var query = dbContext.Set() + .Select(p => new PersonChainChild(p.Id, p.FirstName, "-" + p.LastName)); + + return Verifier.Verify(query.ToQueryString()); + } + } +} + diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt new file mode 100644 index 0000000..e8cf46e --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_BodyAssignments.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PointDto__ctor_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int x, int y) => new global::Foo.PointDto() + { + X = x, + Y = y + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt new file mode 100644 index 0000000..bd7f45c --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_Overloads.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName, string lastName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = lastName + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt new file mode 100644 index 0000000..c2beff8 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string code) => new global::Foo.Child() + { + Code = code, + Label = "[" + (code) + "]" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt new file mode 100644 index 0000000..c2beff8 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingBasePropertyInDerivedBody.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string code) => new global::Foo.Child() + { + Code = code, + Label = "[" + (code) + "]" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt new file mode 100644 index 0000000..ea81d87 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int a, int b) => new global::Foo.Child() + { + X = a, + Y = a + b, + Sum = (a) + (a + b) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt new file mode 100644 index 0000000..ea81d87 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int a, int b) => new global::Foo.Child() + { + X = a, + Y = a + b, + Sum = (a) + (a + b) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt new file mode 100644 index 0000000..ab88351 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName, string lastName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = lastName, + FullName = (firstName) + " " + (lastName) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt new file mode 100644 index 0000000..ab88351 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingPreviouslyAssignedProperty.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName, string lastName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = lastName, + FullName = (firstName) + " " + (lastName) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt new file mode 100644 index 0000000..1bd1677 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = first + global::Foo.PersonDto.Separator + last + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt new file mode 100644 index 0000000..1bd1677 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ReferencingStaticConstMember.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = first + global::Foo.PersonDto.Separator + last + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt new file mode 100644 index 0000000..26be6b2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string_P2_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name, string suffix) => new global::Foo.Child() + { + Id = id, + Name = (name) + suffix + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt new file mode 100644 index 0000000..26be6b2 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ChainedThisAndBase.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string_P2_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name, string suffix) => new global::Foo.Child() + { + Id = id, + Name = (name) + suffix + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt new file mode 100644 index 0000000..34cc6fd --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = "Doe", + FullName = (firstName) + " " + ("Doe") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt new file mode 100644 index 0000000..34cc6fd --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string firstName) => new global::Foo.PersonDto() + { + FirstName = firstName, + LastName = "Doe", + FullName = (firstName) + " " + ("Doe") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt new file mode 100644 index 0000000..a03695d --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fullName) => new global::Foo.PersonDto() + { + FirstName = fullName.Split(' ')[0], + LastName = fullName.Split(' ')[1] + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt new file mode 100644 index 0000000..a03695d --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_SimpleOverload.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fullName) => new global::Foo.PersonDto() + { + FirstName = fullName.Split(' ')[0], + LastName = fullName.Split(' ')[1] + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt new file mode 100644 index 0000000..84c7f49 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.DotNet8_0.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fn, string ln, bool upper) => new global::Foo.PersonDto() + { + FirstName = fn, + LastName = ln, + FullName = upper ? ((fn) + " " + (ln)).ToUpper() : (fn) + " " + (ln) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt new file mode 100644 index 0000000..84c7f49 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithBodyAfter.verified.txt @@ -0,0 +1,21 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string_P2_bool + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string fn, string ln, bool upper) => new global::Foo.PersonDto() + { + FirstName = fn, + LastName = ln, + FullName = upper ? ((fn) + " " + (ln)).ToUpper() : (fn) + " " + (ln) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt new file mode 100644 index 0000000..e5e1f53 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score, string prefix) => new global::Foo.PersonDto() + { + Score = score, + Label = prefix + (score >= 90 ? "A" : "B") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt new file mode 100644 index 0000000..e5e1f53 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_ThisInitializer_WithIfElseInDelegated.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score, string prefix) => new global::Foo.PersonDto() + { + Score = score, + Label = prefix + (score >= 90 ? "A" : "B") + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt new file mode 100644 index 0000000..43c164c --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializer.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name) => new global::Foo.Child() + { + Id = id, + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt new file mode 100644 index 0000000..640a785 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name) => new global::Foo.Child() + { + Id = id < 0 ? 0 : id, + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt new file mode 100644 index 0000000..640a785 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerAndIfElse.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_int_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int id, string name) => new global::Foo.Child() + { + Id = id < 0 ? 0 : id, + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt new file mode 100644 index 0000000..4079aac --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name, string rawCode) => new global::Foo.Child() + { + Code = rawCode.ToUpper(), + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt new file mode 100644 index 0000000..4079aac --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithBaseInitializerExpression.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_Child__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name, string rawCode) => new global::Foo.Child() + { + Code = rawCode.ToUpper(), + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt new file mode 100644 index 0000000..b91f020 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithClassArgument.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_Foo_SourceEntity + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.SourceEntity source) => new global::Foo.PersonDto() + { + Id = source.Id, + Name = source.Name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt new file mode 100644 index 0000000..1d391d0 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name) => new global::Foo.PersonDto() + { + Name = name + }; + } + } +} diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt new file mode 100644 index 0000000..59417ac --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string name) => new global::Foo.PersonDto() + { + Name = name + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt new file mode 100644 index 0000000..09491e5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.DotNet8_0.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Score = score, + Label = score >= 90 ? "A" : "B" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt new file mode 100644 index 0000000..09491e5 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfElseLogic.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Score = score, + Label = score >= 90 ? "A" : "B" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt new file mode 100644 index 0000000..b485526 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Label = score >= 90 ? "A" : "none" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt new file mode 100644 index 0000000..b485526 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithIfNoElse.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_int + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (int score) => new global::Foo.PersonDto() + { + Label = score >= 90 ? "A" : "none" + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt new file mode 100644 index 0000000..ac1d676 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.DotNet8_0.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = (first + " " + last) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt new file mode 100644 index 0000000..ac1d676 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithLocalVariable.verified.txt @@ -0,0 +1,19 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_string_P1_string + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (string first, string last) => new global::Foo.PersonDto() + { + FullName = (first + " " + last) + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt new file mode 100644 index 0000000..fa98281 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.ProjectableConstructor_WithMultipleClassArguments.verified.txt @@ -0,0 +1,20 @@ +// +#nullable disable +using EntityFrameworkCore.Projectables; +using Foo; + +namespace EntityFrameworkCore.Projectables.Generated +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class Foo_PersonDto__ctor_P0_Foo_NamePart_P1_Foo_NamePart + { + static global::System.Linq.Expressions.Expression> Expression() + { + return (global::Foo.NamePart first, global::Foo.NamePart last) => new global::Foo.PersonDto() + { + FirstName = first.Value, + LastName = last.Value + }; + } + } +} \ No newline at end of file diff --git a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs index ea03112..b8d3378 100644 --- a/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTests.cs @@ -3373,6 +3373,698 @@ public int GetDouble() Assert.Empty(result.Diagnostics); } + [Fact] + public Task ProjectableConstructor_BodyAssignments() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PointDto { + public int X { get; set; } + public int Y { get; set; } + + public PointDto() { } + + [Projectable] + public PointDto(int x, int y) { + X = x; + Y = y; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithBaseInitializer() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int Id { get; set; } + public Base(int id) { Id = id; } + + protected Base() { } + } + + class Child : Base { + public string Name { get; set; } + + public Child() { } + + [Projectable] + public Child(int id, string name) : base(id) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_Overloads() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonDto(string fullName) { + FirstName = fullName; + LastName = string.Empty; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Equal(2, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithClassArgument() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class SourceEntity { + public int Id { get; set; } + public string Name { get; set; } + } + + class PersonDto { + public int Id { get; set; } + public string Name { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(SourceEntity source) { + Id = source.Id; + Name = source.Name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithMultipleClassArguments() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class NamePart { + public string Value { get; set; } + } + + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(NamePart first, NamePart last) { + FirstName = first.Value; + LastName = last.Value; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithIfElseLogic() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Label { get; set; } + public int Score { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(int score) { + Score = score; + if (score >= 90) { + Label = ""A""; + } else { + Label = ""B""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithLocalVariable() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FullName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string first, string last) { + var full = first + "" "" + last; + FullName = full; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithBaseInitializerExpression() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public string Code { get; set; } + public Base(string code) { Code = code; } + + protected Base() { } + } + + class Child : Base { + public string Name { get; set; } + + public Child() { } + + [Projectable] + public Child(string name, string rawCode) : base(rawCode.ToUpper()) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithBaseInitializerAndIfElse() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int Id { get; set; } + public Base(int id) { + if (id < 0) { + Id = 0; + } else { + Id = id; + } + } + + protected Base() { } + } + + class Child : Base { + public string Name { get; set; } + + public Child() { } + + [Projectable] + public Child(int id, string name) : base(id) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_WithIfNoElse() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Label { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(int score) { + Label = ""none""; + if (score >= 90) { + Label = ""A""; + } + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingPreviouslyAssignedProperty() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + FullName = FirstName + "" "" + LastName; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingBasePropertyInDerivedBody() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public string Code { get; set; } + public Base(string code) { Code = code; } + + protected Base() { } + } + + class Child : Base { + public string Label { get; set; } + + public Child() { } + + [Projectable] + public Child(string code) : base(code) { + Label = ""["" + Code + ""]""; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingStaticConstMember() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + internal const string Separator = "" - ""; + public string FullName { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string first, string last) { + FullName = first + Separator + last; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ReferencingPreviouslyAssignedInBaseCtor() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int X { get; set; } + public int Y { get; set; } + public Base(int x, int y) { + X = x; + Y = x + y; // Y depends on X's assigned value (x) + } + + protected Base() { } + } + + class Child : Base { + public int Sum { get; set; } + + public Child() { } + + [Projectable] + public Child(int a, int b) : base(a, b) { + Sum = X + Y; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_SimpleOverload() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + + public PersonDto() { } + + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonDto(string fullName) : this(fullName.Split(' ')[0], fullName.Split(' ')[1]) { + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_WithBodyAfter() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonDto() { } + + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + } + + [Projectable] + public PersonDto(string fn, string ln, bool upper) : this(fn, ln) { + FullName = upper ? (FirstName + "" "" + LastName).ToUpper() : FirstName + "" "" + LastName; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_WithIfElseInDelegated() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Label { get; set; } + public int Score { get; set; } + + public PersonDto() { } + + public PersonDto(int score) { + Score = score; + if (score >= 90) { + Label = ""A""; + } else { + Label = ""B""; + } + } + + [Projectable] + public PersonDto(int score, string prefix) : this(score) { + Label = prefix + Label; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_ChainedThisAndBase() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class Base { + public int Id { get; set; } + public Base(int id) { Id = id; } + } + + class Child : Base { + public string Name { get; set; } + + public Child() : base(0) { } + + public Child(int id, string name) : base(id) { + Name = name; + } + + [Projectable] + public Child(int id, string name, string suffix) : this(id, name) { + Name = Name + suffix; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public Task ProjectableConstructor_ThisInitializer_ReferencingPreviouslyAssignedProperty() + { + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public PersonDto() { } + + public PersonDto(string firstName, string lastName) { + FirstName = firstName; + LastName = lastName; + FullName = FirstName + "" "" + LastName; + } + + [Projectable] + public PersonDto(string firstName) : this(firstName, ""Doe"") { + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + + [Fact] + public void ProjectableConstructor_WithoutParameterlessConstructor_EmitsDiagnostic() + { + // A class that only exposes a parameterized constructor (no parameterless one). + // The generator must emit EFP0007 and produce no code because the object-initializer + // pattern requires a parameterless constructor. + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Name { get; set; } + + // No parameterless constructor – only the one marked [Projectable]. + [Projectable] + public PersonDto(string name) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal("EFP0007", diagnostic.Id); + Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); + Assert.Empty(result.GeneratedTrees); + } + + [Fact] + public Task ProjectableConstructor_WithExplicitParameterlessConstructor_Succeeds() + { + // A class that explicitly defines a parameterless constructor alongside the + // [Projectable] one – the generator should succeed and produce code. + var compilation = CreateCompilation(@" +using EntityFrameworkCore.Projectables; + +namespace Foo { + class PersonDto { + public string Name { get; set; } + + public PersonDto() { } + + [Projectable] + public PersonDto(string name) { + Name = name; + } + } +} +"); + var result = RunGenerator(compilation); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedTrees); + + return Verifier.Verify(result.GeneratedTrees[0].ToString()); + } + #region Helpers Compilation CreateCompilation(string source, bool expectedToCompile = true)