mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2026-05-14 07:54:38 +00:00
Merge pull request #28 from bocca/master
UseMemberBody to use the body of another member
This commit is contained in:
@@ -17,5 +17,11 @@ namespace EntityFrameworkCore.Projectables
|
||||
/// Get or set how null-conditional operators are handeled
|
||||
/// </summary>
|
||||
public NullConditionalRewriteSupport NullConditionalRewriteSupport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get or set from which member to get the expression,
|
||||
/// or null to get it from the current member.
|
||||
/// </summary>
|
||||
public string? UseMemberBody { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,70 @@ namespace EntityFrameworkCore.Projectables.Generator
|
||||
.Cast<NullConditionalRewriteSupport>()
|
||||
.FirstOrDefault();
|
||||
|
||||
var useMemberBody = projectableAttributeClass.NamedArguments
|
||||
.Where(x => x.Key == "UseMemberBody")
|
||||
.Select(x => x.Value.Value)
|
||||
.OfType<string?>()
|
||||
.FirstOrDefault();
|
||||
|
||||
var memberBody = member;
|
||||
|
||||
if (useMemberBody is not null)
|
||||
{
|
||||
var comparer = SymbolEqualityComparer.Default;
|
||||
|
||||
memberBody = memberSymbol.ContainingType.GetMembers(useMemberBody)
|
||||
.Where(x =>
|
||||
{
|
||||
if (memberSymbol is IMethodSymbol symbolMethod &&
|
||||
x is IMethodSymbol xMethod &&
|
||||
comparer.Equals(symbolMethod.ReturnType, xMethod.ReturnType) &&
|
||||
symbolMethod.TypeArguments.Length == xMethod.TypeArguments.Length &&
|
||||
!symbolMethod.TypeArguments.Zip(xMethod.TypeArguments, (a, b) => !comparer.Equals(a, b)).Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (memberSymbol is IPropertySymbol symbolProperty &&
|
||||
x is IPropertySymbol xProperty &&
|
||||
comparer.Equals(symbolProperty.Type, xProperty.Type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.SelectMany(x => x.DeclaringSyntaxReferences)
|
||||
.Select(x => x.GetSyntax())
|
||||
.OfType<MemberDeclarationSyntax>()
|
||||
.FirstOrDefault(x =>
|
||||
{
|
||||
if (x == null ||
|
||||
x.SyntaxTree != member.SyntaxTree ||
|
||||
x.Modifiers.Any(SyntaxKind.StaticKeyword) != member.Modifiers.Any(SyntaxKind.StaticKeyword))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (x is MethodDeclarationSyntax xMethod &&
|
||||
xMethod.ExpressionBody is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (x is PropertyDeclarationSyntax xProperty &&
|
||||
xProperty.ExpressionBody is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (memberBody is null) return null;
|
||||
}
|
||||
|
||||
var expressionSyntaxRewriter = new ExpressionSyntaxRewriter(memberSymbol.ContainingType, nullConditionalRewriteSupport, compilation, semanticModel, context);
|
||||
var declarationSyntaxRewriter = new DeclarationSyntaxRewriter(semanticModel);
|
||||
|
||||
@@ -93,7 +157,7 @@ namespace EntityFrameworkCore.Projectables.Generator
|
||||
descriptor.TargetNestedInClassNames = descriptor.NestedInClassNames;
|
||||
}
|
||||
|
||||
if (member is MethodDeclarationSyntax methodDeclarationSyntax)
|
||||
if (memberBody is MethodDeclarationSyntax methodDeclarationSyntax)
|
||||
{
|
||||
if (methodDeclarationSyntax.ExpressionBody is null)
|
||||
{
|
||||
@@ -125,7 +189,7 @@ namespace EntityFrameworkCore.Projectables.Generator
|
||||
.Select(x => (TypeParameterConstraintClauseSyntax)declarationSyntaxRewriter.Visit(x));
|
||||
}
|
||||
}
|
||||
else if (member is PropertyDeclarationSyntax propertyDeclarationSyntax)
|
||||
else if (memberBody is PropertyDeclarationSyntax propertyDeclarationSyntax)
|
||||
{
|
||||
if (propertyDeclarationSyntax.ExpressionBody is null)
|
||||
{
|
||||
|
||||
@@ -22,27 +22,55 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
_ => null
|
||||
};
|
||||
|
||||
var expressionFactoryMethod = reflectedType.Assembly
|
||||
.GetTypes()
|
||||
.Where(x => x.FullName == generatedContainingTypeName)
|
||||
.SelectMany(x => x.GetMethods())
|
||||
.FirstOrDefault();
|
||||
var expressionFactoryMethod = reflectedType.Assembly.GetType(generatedContainingTypeName)
|
||||
?.GetMethods()
|
||||
?.FirstOrDefault();
|
||||
|
||||
if (expressionFactoryMethod is null)
|
||||
if (expressionFactoryMethod is not null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to resolve generated expression") {
|
||||
Data = {
|
||||
["GeneratedContainingTypeName"] = generatedContainingTypeName
|
||||
if (genericArguments is { Length: > 0 })
|
||||
{
|
||||
expressionFactoryMethod = expressionFactoryMethod.MakeGenericMethod(genericArguments);
|
||||
}
|
||||
|
||||
return expressionFactoryMethod.Invoke(null, null) as LambdaExpression ?? throw new InvalidOperationException("Expected lambda");
|
||||
}
|
||||
|
||||
var useMemberBody = projectableMemberInfo.GetCustomAttribute<ProjectableAttribute>()?.UseMemberBody;
|
||||
|
||||
if (useMemberBody is not null)
|
||||
{
|
||||
var exprProperty = reflectedType.GetProperty(useMemberBody, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var lambda = exprProperty?.GetValue(null) as LambdaExpression;
|
||||
|
||||
if (lambda is not null)
|
||||
{
|
||||
if (projectableMemberInfo is PropertyInfo property &&
|
||||
lambda.Parameters.Count == 1 &&
|
||||
lambda.Parameters[0].Type == reflectedType && lambda.ReturnType == property.PropertyType)
|
||||
{
|
||||
return lambda;
|
||||
}
|
||||
};
|
||||
else if (projectableMemberInfo is MethodInfo method &&
|
||||
lambda.Parameters.Count == method.GetParameters().Length + 1 &&
|
||||
lambda.Parameters.Last().Type == reflectedType &&
|
||||
!lambda.Parameters.Zip(method.GetParameters(), (a, b) => a.Type != b.ParameterType).Any())
|
||||
{
|
||||
return lambda;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (genericArguments is { Length: > 0 } )
|
||||
{
|
||||
expressionFactoryMethod = expressionFactoryMethod.MakeGenericMethod(genericArguments);
|
||||
}
|
||||
var fullName = string.Join(".", Enumerable.Empty<string>()
|
||||
.Concat(new[] { reflectedType.Namespace })
|
||||
.Concat(reflectedType.GetNestedTypePath().Select(x => x.Name))
|
||||
.Concat(new[] { projectableMemberInfo.Name }));
|
||||
|
||||
return expressionFactoryMethod.Invoke(null, null) as LambdaExpression ?? throw new InvalidOperationException("Expected lambda");
|
||||
throw new InvalidOperationException($"Unable to resolve generated expression for {fullName}.") {
|
||||
Data = {
|
||||
["GeneratedContainingTypeName"] = generatedContainingTypeName
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
SELECT [e].[Id] * 3
|
||||
FROM [Entity] AS [e]
|
||||
@@ -0,0 +1,2 @@
|
||||
SELECT [e].[Id] * 2
|
||||
FROM [Entity] AS [e]
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlTypes;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ScenarioTests;
|
||||
using VerifyXunit;
|
||||
using Xunit;
|
||||
|
||||
namespace EntityFrameworkCore.Projectables.FunctionalTests
|
||||
{
|
||||
[UsesVerify]
|
||||
public class UseMemberBodyPropertyTests
|
||||
{
|
||||
public record Entity
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Projectable(UseMemberBody = nameof(Computed2))]
|
||||
public int Computed1 => Id;
|
||||
|
||||
private int Computed2 => Id * 2;
|
||||
|
||||
[Projectable(UseMemberBody = nameof(Computed4))]
|
||||
public int Computed3 => Id;
|
||||
|
||||
private static Expression<Func<Entity, int>> Computed4 => x => x.Id * 3;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task UseMemberPropertyGenerated()
|
||||
{
|
||||
using var dbContext = new SampleDbContext<Entity>();
|
||||
|
||||
var query = dbContext.Set<Entity>()
|
||||
.Select(x => x.Computed1);
|
||||
|
||||
return Verifier.Verify(query.ToQueryString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task UseMemberPropertyExpression()
|
||||
{
|
||||
using var dbContext = new SampleDbContext<Entity>();
|
||||
|
||||
var query = dbContext.Set<Entity>()
|
||||
.Select(x => x.Computed3);
|
||||
|
||||
return Verifier.Verify(query.ToQueryString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user