Add project files.

This commit is contained in:
Koen Bekkenutte
2021-05-27 04:17:35 +08:00
commit 1c83f3d6ad
48 changed files with 2554 additions and 0 deletions
@@ -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
{
}
}
@@ -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;
}
}
}
@@ -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));
}
}
}
}
}
@@ -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);
}
}
}
@@ -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();
}
});
});
}
}
}