mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2026-08-15 18:44:57 +00:00
Add project files.
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RootNamespace>EntityFrameworkCore.Projections</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class ProjectableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);nullable;NU5128</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\EntityFrameworkCore.Projections\Services\ProjectionExpressionClassNameGenerator.cs" Link="ProjectionExpressionClassNameGenerator.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Generator
|
||||
{
|
||||
public class ExpressionSyntaxRewriter : CSharpSyntaxRewriter
|
||||
{
|
||||
readonly INamedTypeSymbol _targetTypeSymbol;
|
||||
readonly SemanticModel _semanticModel;
|
||||
|
||||
public ExpressionSyntaxRewriter(INamedTypeSymbol targetTypeSymbol, SemanticModel semanticModel)
|
||||
{
|
||||
_targetTypeSymbol = targetTypeSymbol;
|
||||
_semanticModel = semanticModel;
|
||||
}
|
||||
|
||||
public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
|
||||
{
|
||||
var symbolInfo = _semanticModel.GetSymbolInfo(node);
|
||||
|
||||
if (symbolInfo.Symbol is not null && SymbolEqualityComparer.Default.Equals(symbolInfo.Symbol.ContainingType, _targetTypeSymbol))
|
||||
{
|
||||
var scopedNode = node.ChildNodes().FirstOrDefault();
|
||||
if (scopedNode is ThisExpressionSyntax)
|
||||
{
|
||||
var nextNode = node.ChildNodes().Skip(1).FirstOrDefault() as SimpleNameSyntax;
|
||||
|
||||
if (nextNode is not null)
|
||||
{
|
||||
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.IdentifierName(ProjectionExpressionGenerator.ProjectionTargetParameterName),
|
||||
nextNode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitMemberAccessExpression(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
|
||||
{
|
||||
var symbolInfo = _semanticModel.GetSymbolInfo(node);
|
||||
|
||||
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),
|
||||
node
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.VisitIdentifierName(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Generator
|
||||
{
|
||||
public class ProjectableDescriptor
|
||||
{
|
||||
public IEnumerable<string> UsingDirectives { get; set; }
|
||||
|
||||
public string ClassNamespace { get; set; }
|
||||
|
||||
public IEnumerable<string> NestedInClassNames { get; set; }
|
||||
|
||||
public string ClassName { get; set; }
|
||||
|
||||
public string MemberName { get; set; }
|
||||
|
||||
public string ReturnTypeName { get; set; }
|
||||
|
||||
public string ParametersListString { get; set; }
|
||||
|
||||
public SyntaxNode Body { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Generator
|
||||
{
|
||||
public static class ProjectableInterpreter
|
||||
{
|
||||
static IEnumerable<string> GetNestedInClassPath(INamedTypeSymbol namedTypeSymbol)
|
||||
{
|
||||
if (namedTypeSymbol.ContainingType is not null)
|
||||
{
|
||||
foreach (var nestedInClassName in GetNestedInClassPath(namedTypeSymbol.ContainingType))
|
||||
{
|
||||
yield return nestedInClassName;
|
||||
}
|
||||
}
|
||||
|
||||
yield return namedTypeSymbol.Name;
|
||||
}
|
||||
|
||||
public static ProjectableDescriptor? GetDescriptor(MemberDeclarationSyntax memberDeclarationSyntax, GeneratorExecutionContext context)
|
||||
{
|
||||
var semanticModel = context.Compilation.GetSemanticModel(memberDeclarationSyntax.SyntaxTree);
|
||||
var memberSymbol = semanticModel.GetDeclaredSymbol(memberDeclarationSyntax);
|
||||
|
||||
if (memberSymbol is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var projectableAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("EntityFrameworkCore.Projections.ProjectableAttribute");
|
||||
|
||||
var projectableAttributeClass = memberSymbol.GetAttributes()
|
||||
.Where(x => x.AttributeClass.Name == "ProjectableAttribute")
|
||||
.FirstOrDefault();
|
||||
|
||||
if (projectableAttributeClass is null || !SymbolEqualityComparer.Default.Equals(projectableAttributeClass.AttributeClass, projectableAttributeTypeSymbol))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var expressionSyntaxRewriter = new ExpressionSyntaxRewriter(memberSymbol.ContainingType, semanticModel);
|
||||
|
||||
var descriptor = new ProjectableDescriptor
|
||||
{
|
||||
ClassName = memberSymbol.ContainingType.Name,
|
||||
ClassNamespace = memberSymbol.ContainingType.ContainingNamespace.IsGlobalNamespace ? null : memberSymbol.ContainingType.ContainingNamespace.ToDisplayString(),
|
||||
MemberName = memberSymbol.Name,
|
||||
NestedInClassNames = GetNestedInClassPath(memberSymbol.ContainingType)
|
||||
};
|
||||
|
||||
if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax)
|
||||
{
|
||||
descriptor.ReturnTypeName = methodDeclarationSyntax.ReturnType.ToString();
|
||||
descriptor.Body = expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression);
|
||||
descriptor.ParametersListString = methodDeclarationSyntax.ParameterList.ToString();
|
||||
}
|
||||
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()
|
||||
.DescendantNodes()
|
||||
.OfType<UsingDirectiveSyntax>()
|
||||
.Select(x => x.ToString());
|
||||
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using EntityFrameworkCore.Projections.Services;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (receiver.Candidates.Count > 0)
|
||||
{
|
||||
var projectables = receiver.Candidates
|
||||
.Select(x => ProjectableInterpreter.GetDescriptor(x, context))
|
||||
.Where(x => x is not null);
|
||||
|
||||
var resultBuilder = new StringBuilder();
|
||||
|
||||
foreach (var projectable in projectables)
|
||||
{
|
||||
resultBuilder.Clear();
|
||||
|
||||
foreach (var usingDirective in projectable.UsingDirectives)
|
||||
{
|
||||
resultBuilder.AppendLine(usingDirective);
|
||||
}
|
||||
|
||||
var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName);
|
||||
|
||||
resultBuilder.Append($@"
|
||||
namespace EntityFrameworkCore.Projections.Generated
|
||||
#nullable disable
|
||||
{{
|
||||
public static class {generatedClassName}
|
||||
{{
|
||||
public static System.Linq.Expressions.Expression<System.Func<{projectable.ClassNamespace}.{string.Join(".", projectable.NestedInClassNames)}, {projectable.ReturnTypeName}>> Expression{projectable.ParametersListString} =>
|
||||
{ProjectionTargetParameterName} => {projectable.Body};
|
||||
}}
|
||||
}}");
|
||||
|
||||
context.AddSource($"{generatedClassName}_Generated", SourceText.From(resultBuilder.ToString(), Encoding.UTF8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context) =>
|
||||
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Generator
|
||||
{
|
||||
public class SyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<MemberDeclarationSyntax> Candidates { get; } = new List<MemberDeclarationSyntax>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is MemberDeclarationSyntax memberDeclarationSyntax && memberDeclarationSyntax.AttributeLists.Count > 0)
|
||||
{
|
||||
var hasProjectableAttribute = memberDeclarationSyntax.AttributeLists
|
||||
.SelectMany(x => x.Attributes)
|
||||
.Any(x => x.Name.ToString().Contains("Projectable"));
|
||||
|
||||
if (hasProjectableAttribute)
|
||||
{
|
||||
Candidates.Add(memberDeclarationSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EntityFrameworkCore.Projections.Abstractions\EntityFrameworkCore.Projections.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,27 @@
|
||||
using EntityFrameworkCore.Projections.Infrastructure.Internal;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Extensions
|
||||
{
|
||||
public static class DbContextOptionsExtensions
|
||||
{
|
||||
public static DbContextOptionsBuilder<TContext> UseProjections<TContext>(this DbContextOptionsBuilder<TContext> optionsBuilder)
|
||||
where TContext : DbContext
|
||||
=> (DbContextOptionsBuilder<TContext>)UseProjections((DbContextOptionsBuilder)optionsBuilder);
|
||||
|
||||
public static DbContextOptionsBuilder UseProjections(this DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
var extension = optionsBuilder.Options.FindExtension<ProjectionOptionsExtension>() ?? new ProjectionOptionsExtension();
|
||||
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
|
||||
|
||||
return optionsBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Extensions
|
||||
{
|
||||
public static class TypeExtensions
|
||||
{
|
||||
public static IEnumerable<Type> GetNestedTypePath(this Type type)
|
||||
{
|
||||
if (type.IsNested && type.DeclaringType is not null)
|
||||
{
|
||||
foreach (var containingType in type.DeclaringType.GetNestedTypePath())
|
||||
{
|
||||
yield return containingType;
|
||||
}
|
||||
}
|
||||
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Infrastructure.Internal
|
||||
{
|
||||
public class ProjectionOptionsExtension : IDbContextOptionsExtension
|
||||
{
|
||||
public ProjectionOptionsExtension()
|
||||
{
|
||||
Info = new ExtensionInfo(this);
|
||||
}
|
||||
|
||||
public DbContextOptionsExtensionInfo Info { get; }
|
||||
|
||||
public void ApplyServices(IServiceCollection services)
|
||||
{
|
||||
var existingPreprocessorFactoryRegistration = services.FirstOrDefault(x => x.ServiceType == typeof(IQueryTranslationPreprocessorFactory));
|
||||
|
||||
if (existingPreprocessorFactoryRegistration?.ImplementationType is null)
|
||||
{
|
||||
throw new InvalidOperationException("Expected a QueryTranslationPreprocessor to be registered. Please make sure to register your database provider first");
|
||||
}
|
||||
|
||||
// Ensure that we can still resolve this factory
|
||||
services.Add(new ServiceDescriptor(existingPreprocessorFactoryRegistration.ImplementationType, existingPreprocessorFactoryRegistration.ImplementationType, existingPreprocessorFactoryRegistration.Lifetime));
|
||||
services.Remove(existingPreprocessorFactoryRegistration);
|
||||
|
||||
services.Add(new ServiceDescriptor(
|
||||
typeof(IQueryTranslationPreprocessorFactory),
|
||||
serviceProvider => new WrappedQueryTranslationPreprocessorFactory((IQueryTranslationPreprocessorFactory)serviceProvider.GetRequiredService(existingPreprocessorFactoryRegistration.ImplementationType), serviceProvider.GetRequiredService<QueryTranslationPreprocessorDependencies>()),
|
||||
existingPreprocessorFactoryRegistration.Lifetime
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
public void Validate(IDbContextOptions options)
|
||||
{
|
||||
}
|
||||
|
||||
sealed class ExtensionInfo : DbContextOptionsExtensionInfo
|
||||
{
|
||||
public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsDatabaseProvider => false;
|
||||
public override string LogFragment => string.Empty;
|
||||
public override long GetServiceProviderHashCode() => 0;
|
||||
|
||||
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
|
||||
{
|
||||
if (debugInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(debugInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using EntityFrameworkCore.Projections.Services;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Infrastructure.Internal
|
||||
{
|
||||
public class WrappedQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
|
||||
{
|
||||
readonly IQueryTranslationPreprocessorFactory _originalFactory;
|
||||
readonly QueryTranslationPreprocessorDependencies _dependencies;
|
||||
|
||||
public WrappedQueryTranslationPreprocessorFactory(IQueryTranslationPreprocessorFactory originalFactory, QueryTranslationPreprocessorDependencies dependencies)
|
||||
{
|
||||
_originalFactory = originalFactory;
|
||||
_dependencies = dependencies;
|
||||
}
|
||||
|
||||
public QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
|
||||
{
|
||||
var originalPreprocessor = _originalFactory.Create(queryCompilationContext);
|
||||
|
||||
return new WrappedQueryTranslationPreprocessor(originalPreprocessor, _dependencies, queryCompilationContext);
|
||||
}
|
||||
}
|
||||
|
||||
public class WrappedQueryTranslationPreprocessor : QueryTranslationPreprocessor
|
||||
{
|
||||
readonly ProjectableExpressionReplacer _projectableExpressionReplacer;
|
||||
readonly QueryTranslationPreprocessor _originalPreprocessor;
|
||||
|
||||
public WrappedQueryTranslationPreprocessor(QueryTranslationPreprocessor originalPreprocessor, QueryTranslationPreprocessorDependencies dependencies, QueryCompilationContext queryCompilationContext) : base(dependencies, queryCompilationContext)
|
||||
{
|
||||
_originalPreprocessor = originalPreprocessor;
|
||||
_projectableExpressionReplacer = new ProjectableExpressionReplacer();
|
||||
}
|
||||
|
||||
public override Expression NormalizeQueryableMethod(Expression expression)
|
||||
{
|
||||
return _originalPreprocessor.NormalizeQueryableMethod(expression);
|
||||
}
|
||||
|
||||
public override Expression Process(Expression query)
|
||||
{
|
||||
query = _projectableExpressionReplacer.Visit(query);
|
||||
|
||||
return _originalPreprocessor.Process(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Services
|
||||
{
|
||||
public class ExpressionArgumentReplacer : ExpressionVisitor
|
||||
{
|
||||
readonly Expression _targetExpression;
|
||||
|
||||
public ExpressionArgumentReplacer(Expression targetExpression)
|
||||
{
|
||||
_targetExpression = targetExpression;
|
||||
}
|
||||
|
||||
protected override Expression VisitParameter(ParameterExpression node)
|
||||
{
|
||||
if (node.Name == "projectionTarget")
|
||||
{
|
||||
return _targetExpression;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.VisitParameter(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Services
|
||||
{
|
||||
public class ProjectableExpressionReplacer : ExpressionVisitor
|
||||
{
|
||||
readonly ProjectionExpressionResolver _resolver = new();
|
||||
|
||||
protected override Expression VisitMethodCall(MethodCallExpression node)
|
||||
{
|
||||
if (node.Method.GetCustomAttributes(true).OfType<ProjectableAttribute>().Any())
|
||||
{
|
||||
var reflectedExpressionFactory = _resolver.FindGeneratedExpressionFactory(node.Method);
|
||||
var reflectedExpresssion = reflectedExpressionFactory(node.Arguments);
|
||||
if (reflectedExpresssion is not null)
|
||||
{
|
||||
if (node.Object is not null)
|
||||
{
|
||||
var expressionArgumentReplacer = new ExpressionArgumentReplacer(node.Object);
|
||||
return expressionArgumentReplacer.Visit(reflectedExpresssion.Body);
|
||||
}
|
||||
else
|
||||
{
|
||||
return reflectedExpresssion.Body;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitMethodCall(node);
|
||||
}
|
||||
|
||||
protected override Expression VisitMember(MemberExpression node)
|
||||
{
|
||||
if (node.Member.GetCustomAttributes(true).OfType<ProjectableAttribute>().Any())
|
||||
{
|
||||
var reflectedExpressionFactory = _resolver.FindGeneratedExpressionFactory(node.Member);
|
||||
var reflectedExpression = reflectedExpressionFactory(null);
|
||||
if (reflectedExpression is not null)
|
||||
{
|
||||
if (node.Expression is not null)
|
||||
{
|
||||
var expressionArgumentReplacer = new ExpressionArgumentReplacer(node.Expression);
|
||||
return expressionArgumentReplacer.Visit(reflectedExpression.Body);
|
||||
}
|
||||
else
|
||||
{
|
||||
return reflectedExpression.Body;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitMember(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Services
|
||||
{
|
||||
public static class ProjectionExpressionClassNameGenerator
|
||||
{
|
||||
public const string Namespace = "EntityFrameworkCore.Projections.Generated";
|
||||
|
||||
public static string GenerateName(string? namespaceName, IEnumerable<string> nestedInClassNames, string memberName)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
return GenerateNameImpl(stringBuilder, namespaceName, nestedInClassNames, memberName);
|
||||
}
|
||||
|
||||
public static string GenerateFullName(string? namespaceName, IEnumerable<string> nestedInClassNames, string memberName)
|
||||
{
|
||||
var stringBuilder = new StringBuilder(Namespace);
|
||||
stringBuilder.Append('.');
|
||||
|
||||
return GenerateNameImpl(stringBuilder, namespaceName, nestedInClassNames, memberName);
|
||||
}
|
||||
|
||||
static string GenerateNameImpl(StringBuilder stringBuilder, string? namespaceName, IEnumerable<string> nestedInClassNames, string memberName)
|
||||
{
|
||||
stringBuilder.Append(namespaceName?.Replace('.', '_'));
|
||||
stringBuilder.Append('_');
|
||||
foreach (var className in nestedInClassNames)
|
||||
{
|
||||
stringBuilder.Append(className);
|
||||
stringBuilder.Append('_');
|
||||
}
|
||||
stringBuilder.Append(memberName);
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EntityFrameworkCore.Projections.Extensions;
|
||||
|
||||
namespace EntityFrameworkCore.Projections.Services
|
||||
{
|
||||
public sealed class ProjectionExpressionResolver
|
||||
{
|
||||
readonly ConcurrentDictionary<string, Func<IReadOnlyCollection<Expression>?, LambdaExpression>> _lookupCache = new();
|
||||
|
||||
public Func<IReadOnlyCollection<Expression>?, LambdaExpression> FindGeneratedExpressionFactory(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);
|
||||
|
||||
return _lookupCache.GetOrAdd(generatedContainingTypeName, _ => {
|
||||
var expressionFactoryMethod = reflectedType.Assembly
|
||||
.GetTypes()
|
||||
.Where(x => x.FullName == generatedContainingTypeName)
|
||||
.SelectMany(x => x.GetMethods())
|
||||
.FirstOrDefault();
|
||||
|
||||
if (expressionFactoryMethod is null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to resolve generated expression") {
|
||||
Data = {
|
||||
["GeneratedContainingTypeName"] = generatedContainingTypeName
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 test1 = argumentExpressions.Cast<ParameterExpression>()!;
|
||||
|
||||
var expressionFactoryConstructionMethod =
|
||||
Expression.Lambda<Func<LambdaExpression>>(
|
||||
Expression.Call(
|
||||
expressionFactoryMethod,
|
||||
argumentExpressions
|
||||
)
|
||||
).Compile();
|
||||
|
||||
return expressionFactoryConstructionMethod.Invoke();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user