mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2026-08-15 18:44:57 +00:00
Rewrite of most internals
This commit is contained in:
@@ -35,7 +35,7 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
if (nextNode is not null)
|
||||
{
|
||||
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.IdentifierName(ProjectionExpressionGenerator.ProjectionTargetParameterName),
|
||||
SyntaxFactory.IdentifierName("@this"),
|
||||
nextNode
|
||||
);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
if (symbolInfo.Symbol is not null && symbolInfo.Symbol.Kind is SymbolKind.Property or SymbolKind.Method or SymbolKind.Field && SymbolEqualityComparer.Default.Equals(symbolInfo.Symbol.ContainingType, _targetTypeSymbol))
|
||||
{
|
||||
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.IdentifierName(ProjectionExpressionGenerator.ProjectionTargetParameterName),
|
||||
SyntaxFactory.IdentifierName("@this"),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
@@ -14,28 +15,27 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
_semanticModel = semanticModel;
|
||||
}
|
||||
|
||||
public override SyntaxNode? VisitQualifiedName(QualifiedNameSyntax node)
|
||||
{
|
||||
// todo: Fully qualify these
|
||||
return node;
|
||||
}
|
||||
|
||||
public override SyntaxNode? VisitParameter(ParameterSyntax node)
|
||||
public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
|
||||
{
|
||||
var symbol = _semanticModel.GetDeclaredSymbol(node);
|
||||
|
||||
if (symbol is not null)
|
||||
{
|
||||
node = SyntaxFactory.IdentifierName(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
|
||||
}
|
||||
|
||||
return base.VisitIdentifierName(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode? VisitParameter(ParameterSyntax node)
|
||||
{
|
||||
var thisKeywordIndex = node.Modifiers.IndexOf(SyntaxKind.ThisKeyword);
|
||||
if (thisKeywordIndex != -1)
|
||||
{
|
||||
node = node.WithModifiers(node.Modifiers.RemoveAt(thisKeywordIndex));
|
||||
}
|
||||
|
||||
|
||||
return node;
|
||||
return base.VisitParameter(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -25,7 +26,7 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
|
||||
public string ReturnTypeName { get; set; }
|
||||
|
||||
public string ParametersListString { get; set; }
|
||||
public ParameterListSyntax ParametersList { get; set; }
|
||||
|
||||
public SyntaxNode Body { get; set; }
|
||||
}
|
||||
|
||||
@@ -50,9 +50,26 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
ClassName = memberSymbol.ContainingType.Name,
|
||||
ClassNamespace = memberSymbol.ContainingType.ContainingNamespace.IsGlobalNamespace ? null : memberSymbol.ContainingType.ContainingNamespace.ToDisplayString(),
|
||||
MemberName = memberSymbol.Name,
|
||||
NestedInClassNames = GetNestedInClassPath(memberSymbol.ContainingType)
|
||||
NestedInClassNames = GetNestedInClassPath(memberSymbol.ContainingType),
|
||||
ParametersList = SyntaxFactory.ParameterList()
|
||||
};
|
||||
|
||||
if (!memberDeclarationSyntax.Modifiers.Any(SyntaxKind.StaticKeyword))
|
||||
{
|
||||
descriptor.ParametersList = descriptor.ParametersList.AddParameters(
|
||||
SyntaxFactory.Parameter(
|
||||
SyntaxFactory.Identifier("@this")
|
||||
).WithType(
|
||||
SyntaxFactory.ParseTypeName(
|
||||
memberSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
)
|
||||
.WithTrailingTrivia(
|
||||
SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ")
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (memberSymbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionMethod)
|
||||
{
|
||||
var targetTypeSymbol = methodSymbol.Parameters.First().Type;
|
||||
@@ -69,19 +86,23 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
{
|
||||
descriptor.ReturnTypeName = methodDeclarationSyntax.ReturnType.ToString();
|
||||
descriptor.Body = expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression);
|
||||
descriptor.ParametersListString = parameterSyntaxRewriter.Visit(methodDeclarationSyntax.ParameterList).ToString();
|
||||
foreach (var additionalParameter in ((ParameterListSyntax)parameterSyntaxRewriter.Visit(methodDeclarationSyntax.ParameterList)).Parameters)
|
||||
{
|
||||
descriptor.ParametersList = descriptor.ParametersList.AddParameters(additionalParameter);
|
||||
}
|
||||
}
|
||||
else if (memberDeclarationSyntax is PropertyDeclarationSyntax propertyDeclarationSyntax)
|
||||
{
|
||||
descriptor.ReturnTypeName = propertyDeclarationSyntax.Type.ToString();
|
||||
descriptor.Body = expressionSyntaxRewriter.Visit(propertyDeclarationSyntax.ExpressionBody.Expression);
|
||||
descriptor.ParametersListString = "()";
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
descriptor.UsingDirectives =
|
||||
memberDeclarationSyntax.SyntaxTree
|
||||
.GetRoot()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using EntityFrameworkCore.Projections.Services;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -12,8 +13,6 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
[Generator]
|
||||
public class ProjectionExpressionGenerator : ISourceGenerator
|
||||
{
|
||||
public const string ProjectionTargetParameterName = "projectionTarget";
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
|
||||
@@ -49,6 +48,11 @@ namespace EntityFrameworkCore.Projections.Generator
|
||||
}
|
||||
|
||||
var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName);
|
||||
var lambdaTypeArguments = SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SeparatedList(
|
||||
projectable.ParametersList.Parameters.Select(p => p.Type)
|
||||
)
|
||||
);
|
||||
|
||||
resultBuilder.Append($@"
|
||||
namespace EntityFrameworkCore.Projections.Generated
|
||||
@@ -56,8 +60,8 @@ namespace EntityFrameworkCore.Projections.Generated
|
||||
{{
|
||||
public static class {generatedClassName}
|
||||
{{
|
||||
public static System.Linq.Expressions.Expression<System.Func<{projectable.TargetClassNamespace}.{string.Join(".", projectable.TargetNestedInClassNames)}, {projectable.ReturnTypeName}>> Expression{projectable.ParametersListString} =>
|
||||
{ProjectionTargetParameterName} => {projectable.Body};
|
||||
public static System.Linq.Expressions.Expression<System.Func<{lambdaTypeArguments.Arguments}, {projectable.ReturnTypeName}>> Expression =>
|
||||
{projectable.ParametersList} => {projectable.Body};
|
||||
}}
|
||||
}}");
|
||||
|
||||
|
||||
@@ -9,23 +9,26 @@ namespace EntityFrameworkCore.Projections.Services
|
||||
{
|
||||
public class ExpressionArgumentReplacer : ExpressionVisitor
|
||||
{
|
||||
readonly Expression _targetExpression;
|
||||
readonly IEnumerable<(ParameterExpression parameter, Expression argument)>? _parameterArgumentMapping;
|
||||
|
||||
public ExpressionArgumentReplacer(Expression targetExpression)
|
||||
public ExpressionArgumentReplacer(IEnumerable<(ParameterExpression, Expression)>? parameterArgumentMapping = null)
|
||||
{
|
||||
_targetExpression = targetExpression;
|
||||
_parameterArgumentMapping = parameterArgumentMapping;
|
||||
}
|
||||
|
||||
protected override Expression VisitParameter(ParameterExpression node)
|
||||
{
|
||||
if (node.Name == "projectionTarget")
|
||||
var mappedArgument = _parameterArgumentMapping?
|
||||
.Where(x => x.parameter == node)
|
||||
.Select(x => x.argument)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (mappedArgument is not null)
|
||||
{
|
||||
return _targetExpression;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.VisitParameter(node);
|
||||
return mappedArgument;
|
||||
}
|
||||
|
||||
return base.VisitParameter(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,24 +15,25 @@ namespace EntityFrameworkCore.Projections.Services
|
||||
{
|
||||
if (node.Method.GetCustomAttributes(true).OfType<ProjectableAttribute>().Any())
|
||||
{
|
||||
var reflectedExpressionFactory = _resolver.FindGeneratedExpressionFactory(node.Method);
|
||||
var reflectedExpresssion = reflectedExpressionFactory(node.Arguments);
|
||||
if (reflectedExpresssion is not null)
|
||||
var reflectedExpression = _resolver.FindGeneratedExpression(node.Method);
|
||||
|
||||
var parameterArgumentMapping = node.Object is not null
|
||||
? Enumerable.Repeat((reflectedExpression.Parameters[0], node.Object), 1)
|
||||
: Enumerable.Empty<(ParameterExpression, Expression)>();
|
||||
|
||||
if (reflectedExpression.Parameters.Count > 0)
|
||||
{
|
||||
if (node.Object is not null)
|
||||
{
|
||||
var expressionArgumentReplacer = new ExpressionArgumentReplacer(node.Object);
|
||||
return Visit(
|
||||
expressionArgumentReplacer.Visit(reflectedExpresssion.Body)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visit(
|
||||
reflectedExpresssion.Body
|
||||
);
|
||||
}
|
||||
parameterArgumentMapping = parameterArgumentMapping.Concat(
|
||||
node.Object is not null
|
||||
? reflectedExpression.Parameters.Skip(1).Zip(node.Arguments)
|
||||
: reflectedExpression.Parameters.Zip(node.Arguments)
|
||||
);
|
||||
}
|
||||
|
||||
var expressionArgumentReplacer = new ExpressionArgumentReplacer(parameterArgumentMapping);
|
||||
return Visit(
|
||||
expressionArgumentReplacer.Visit(reflectedExpression.Body)
|
||||
);
|
||||
}
|
||||
|
||||
return base.VisitMethodCall(node);
|
||||
@@ -40,25 +41,24 @@ namespace EntityFrameworkCore.Projections.Services
|
||||
|
||||
protected override Expression VisitMember(MemberExpression node)
|
||||
{
|
||||
if (node.Member.GetCustomAttributes(true).OfType<ProjectableAttribute>().Any())
|
||||
if (node.Member.GetCustomAttributes(false).OfType<ProjectableAttribute>().Any())
|
||||
{
|
||||
var reflectedExpressionFactory = _resolver.FindGeneratedExpressionFactory(node.Member);
|
||||
var reflectedExpression = reflectedExpressionFactory(null);
|
||||
if (reflectedExpression is not null)
|
||||
var reflectedExpression = _resolver.FindGeneratedExpression(node.Member);
|
||||
|
||||
if (node.Expression is not null)
|
||||
{
|
||||
if (node.Expression is not null)
|
||||
{
|
||||
var expressionArgumentReplacer = new ExpressionArgumentReplacer(node.Expression);
|
||||
return Visit(
|
||||
expressionArgumentReplacer.Visit(reflectedExpression.Body)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visit(
|
||||
reflectedExpression.Body
|
||||
);
|
||||
}
|
||||
var expressionArgumentReplacer = new ExpressionArgumentReplacer(
|
||||
Enumerable.Repeat((reflectedExpression.Parameters[0], node.Expression), 1)
|
||||
);
|
||||
return Visit(
|
||||
expressionArgumentReplacer.Visit(reflectedExpression.Body)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visit(
|
||||
reflectedExpression.Body
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace EntityFrameworkCore.Projections.Services
|
||||
{
|
||||
public sealed class ProjectionExpressionResolver
|
||||
{
|
||||
readonly ConcurrentDictionary<string, Func<IReadOnlyCollection<Expression>?, LambdaExpression>> _lookupCache = new();
|
||||
readonly ConcurrentDictionary<string, LambdaExpression> _lookupCache = new();
|
||||
|
||||
public Func<IReadOnlyCollection<Expression>?, LambdaExpression> FindGeneratedExpressionFactory(MemberInfo projectableMemberInfo)
|
||||
public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo)
|
||||
{
|
||||
var reflectedType = projectableMemberInfo.ReflectedType ?? throw new InvalidOperationException("Expected a valid type here");
|
||||
var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(reflectedType.Namespace, reflectedType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name);
|
||||
@@ -35,32 +35,7 @@ namespace EntityFrameworkCore.Projections.Services
|
||||
};
|
||||
}
|
||||
|
||||
return new Func<IReadOnlyCollection<Expression>?, LambdaExpression>(argumentExpressions =>
|
||||
{
|
||||
if (argumentExpressions is null || argumentExpressions.Count is 0)
|
||||
{
|
||||
return expressionFactoryMethod.Invoke(null, null) as LambdaExpression ?? throw new InvalidOperationException("Expected lambda");
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameterExtractor = new ParameterExtractor();
|
||||
foreach (var argument in argumentExpressions)
|
||||
{
|
||||
parameterExtractor.Visit(argument);
|
||||
}
|
||||
|
||||
var lambda = Expression.Lambda<Func<LambdaExpression>>(
|
||||
Expression.Call(
|
||||
expressionFactoryMethod,
|
||||
argumentExpressions
|
||||
),
|
||||
parameterExtractor.ExtractedParameters
|
||||
);
|
||||
|
||||
|
||||
return lambda.Compile().Invoke();
|
||||
}
|
||||
});
|
||||
return expressionFactoryMethod.Invoke(null, null) as LambdaExpression ?? throw new InvalidOperationException("Expected lambda");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user