Checkpoint (still hacking about)

This commit is contained in:
Koen Bekkenutte
2021-05-28 03:07:16 +08:00
parent d808b69310
commit 0d7bec4bd6
30 changed files with 413 additions and 98 deletions

View File

@@ -10,6 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -4,9 +4,11 @@ using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -26,6 +28,14 @@ namespace BasicSample
[Projectable] [Projectable]
public double TotalSpent => Orders.Sum(x => x.PriceSum); public double TotalSpent => Orders.Sum(x => x.PriceSum);
[Projectable]
public Order MostValuableOrder
=> Orders.OrderByDescending(x => x.PriceSum).FirstOrDefault();
[Projectable]
public IEnumerable<Product> FindOrderedProducts(string namePrefix)
=> Orders.SelectMany(x => x.Items).Select(x => x.Product).Where(x => x.Name.StartsWith(namePrefix));
} }
public class Product public class Product
@@ -85,7 +95,8 @@ namespace BasicSample
.AddDbContext<ApplicationDbContext>(options => { .AddDbContext<ApplicationDbContext>(options => {
options options
.UseSqlite(dbConnection) .UseSqlite(dbConnection)
.UseProjections(); .UseProjections()
.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
}) })
.BuildServiceProvider(); .BuildServiceProvider();
@@ -119,17 +130,43 @@ namespace BasicSample
dbContext.Users.Add(user); dbContext.Users.Add(user);
dbContext.SaveChanges(); dbContext.SaveChanges();
var query = dbContext.Users // What did our user spent in total
.Select(x => new { {
Name = x.FullName, var query = dbContext.Users
x.TotalSpent .Select(x => new {
}); Name = x.FullName,
x.TotalSpent
});
var result = query.FirstOrDefault(); var result = query.FirstOrDefault();
Console.WriteLine($"Our user ({result.Name}) spent {result.TotalSpent}");
}
{
var query = dbContext.Users
.Select(x => new {
Name = x.FullName,
x.MostValuableOrder
});
var result = query.FirstOrDefault();
Console.WriteLine($"Our users spent {result.MostValuableOrder.PriceSum} on its biggest order");
}
{
var query = dbContext.Users
.Select(x => new {
Name = x.FullName,
Ordered = x.FindOrderedProducts("Red").Select(x => x.Name)
});
var result = query.FirstOrDefault();
Console.WriteLine($"Our users bought the following products starting with 'Red': {string.Join(", ", result.Ordered)}");
}
Console.WriteLine($"Our user {result.Name} spent {result.TotalSpent}");
Console.WriteLine($"We used the following query:");
Console.WriteLine(query.ToQueryString());
} }
} }
} }

View File

@@ -9,6 +9,7 @@ using System.Threading.Tasks;
namespace EntityFrameworkCore.Projections.Generator namespace EntityFrameworkCore.Projections.Generator
{ {
public class ExpressionSyntaxRewriter : CSharpSyntaxRewriter public class ExpressionSyntaxRewriter : CSharpSyntaxRewriter
{ {
readonly INamedTypeSymbol _targetTypeSymbol; readonly INamedTypeSymbol _targetTypeSymbol;

View File

@@ -0,0 +1,41 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace EntityFrameworkCore.Projections.Generator
{
public class ParameterSyntaxRewriter : CSharpSyntaxRewriter
{
readonly SemanticModel _semanticModel;
public ParameterSyntaxRewriter(SemanticModel semanticModel)
{
_semanticModel = semanticModel;
}
public override SyntaxNode? VisitQualifiedName(QualifiedNameSyntax node)
{
// todo: Fully qualify these
return node;
}
public override SyntaxNode? VisitParameter(ParameterSyntax node)
{
var symbol = _semanticModel.GetDeclaredSymbol(node);
if (symbol is not null)
{
}
var thisKeywordIndex = node.Modifiers.IndexOf(SyntaxKind.ThisKeyword);
if (thisKeywordIndex != -1)
{
node = node.WithModifiers(node.Modifiers.RemoveAt(thisKeywordIndex));
}
return node;
}
}
}

View File

@@ -15,6 +15,10 @@ namespace EntityFrameworkCore.Projections.Generator
public IEnumerable<string> NestedInClassNames { get; set; } public IEnumerable<string> NestedInClassNames { get; set; }
public string TargetClassNamespace { get; set; }
public IEnumerable<string> TargetNestedInClassNames { get; set; }
public string ClassName { get; set; } public string ClassName { get; set; }
public string MemberName { get; set; } public string MemberName { get; set; }

View File

@@ -1,17 +1,14 @@
using Microsoft.CodeAnalysis; using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax; 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 namespace EntityFrameworkCore.Projections.Generator
{ {
public static class ProjectableInterpreter public static class ProjectableInterpreter
{ {
static IEnumerable<string> GetNestedInClassPath(INamedTypeSymbol namedTypeSymbol) static IEnumerable<string> GetNestedInClassPath(ITypeSymbol namedTypeSymbol)
{ {
if (namedTypeSymbol.ContainingType is not null) if (namedTypeSymbol.ContainingType is not null)
{ {
@@ -34,6 +31,7 @@ namespace EntityFrameworkCore.Projections.Generator
return null; return null;
} }
var projectableAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("EntityFrameworkCore.Projections.ProjectableAttribute"); var projectableAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("EntityFrameworkCore.Projections.ProjectableAttribute");
var projectableAttributeClass = memberSymbol.GetAttributes() var projectableAttributeClass = memberSymbol.GetAttributes()
@@ -46,20 +44,32 @@ namespace EntityFrameworkCore.Projections.Generator
} }
var expressionSyntaxRewriter = new ExpressionSyntaxRewriter(memberSymbol.ContainingType, semanticModel); var expressionSyntaxRewriter = new ExpressionSyntaxRewriter(memberSymbol.ContainingType, semanticModel);
var parameterSyntaxRewriter = new ParameterSyntaxRewriter(semanticModel);
var descriptor = new ProjectableDescriptor var descriptor = new ProjectableDescriptor {
{
ClassName = memberSymbol.ContainingType.Name, ClassName = memberSymbol.ContainingType.Name,
ClassNamespace = memberSymbol.ContainingType.ContainingNamespace.IsGlobalNamespace ? null : memberSymbol.ContainingType.ContainingNamespace.ToDisplayString(), ClassNamespace = memberSymbol.ContainingType.ContainingNamespace.IsGlobalNamespace ? null : memberSymbol.ContainingType.ContainingNamespace.ToDisplayString(),
MemberName = memberSymbol.Name, MemberName = memberSymbol.Name,
NestedInClassNames = GetNestedInClassPath(memberSymbol.ContainingType) NestedInClassNames = GetNestedInClassPath(memberSymbol.ContainingType)
}; };
if (memberSymbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionMethod)
{
var targetTypeSymbol = methodSymbol.Parameters.First().Type;
descriptor.TargetClassNamespace = targetTypeSymbol.ContainingNamespace.IsGlobalNamespace ? null : targetTypeSymbol.ContainingNamespace.ToDisplayString();
descriptor.TargetNestedInClassNames = GetNestedInClassPath(targetTypeSymbol);
}
else
{
descriptor.TargetClassNamespace = descriptor.ClassNamespace;
descriptor.TargetNestedInClassNames = descriptor.NestedInClassNames;
}
if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax) if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax)
{ {
descriptor.ReturnTypeName = methodDeclarationSyntax.ReturnType.ToString(); descriptor.ReturnTypeName = methodDeclarationSyntax.ReturnType.ToString();
descriptor.Body = expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression); descriptor.Body = expressionSyntaxRewriter.Visit(methodDeclarationSyntax.ExpressionBody.Expression);
descriptor.ParametersListString = methodDeclarationSyntax.ParameterList.ToString(); descriptor.ParametersListString = parameterSyntaxRewriter.Visit(methodDeclarationSyntax.ParameterList).ToString();
} }
else if (memberDeclarationSyntax is PropertyDeclarationSyntax propertyDeclarationSyntax) else if (memberDeclarationSyntax is PropertyDeclarationSyntax propertyDeclarationSyntax)
{ {

View File

@@ -38,6 +38,16 @@ namespace EntityFrameworkCore.Projections.Generator
resultBuilder.AppendLine(usingDirective); resultBuilder.AppendLine(usingDirective);
} }
if (projectable.TargetClassNamespace is not null && !projectable.UsingDirectives.Contains(projectable.TargetClassNamespace))
{
resultBuilder.AppendLine($"using {projectable.TargetClassNamespace};");
}
if (projectable.ClassNamespace is not null && projectable.ClassNamespace != projectable.TargetClassNamespace && !projectable.UsingDirectives.Contains(projectable.ClassNamespace))
{
resultBuilder.AppendLine($"using {projectable.ClassNamespace};");
}
var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName); var generatedClassName = ProjectionExpressionClassNameGenerator.GenerateName(projectable.ClassNamespace, projectable.NestedInClassNames, projectable.MemberName);
resultBuilder.Append($@" resultBuilder.Append($@"
@@ -46,7 +56,7 @@ namespace EntityFrameworkCore.Projections.Generated
{{ {{
public static class {generatedClassName} public static class {generatedClassName}
{{ {{
public static System.Linq.Expressions.Expression<System.Func<{projectable.ClassNamespace}.{string.Join(".", projectable.NestedInClassNames)}, {projectable.ReturnTypeName}>> Expression{projectable.ParametersListString} => public static System.Linq.Expressions.Expression<System.Func<{projectable.TargetClassNamespace}.{string.Join(".", projectable.TargetNestedInClassNames)}, {projectable.ReturnTypeName}>> Expression{projectable.ParametersListString} =>
{ProjectionTargetParameterName} => {projectable.Body}; {ProjectionTargetParameterName} => {projectable.Body};
}} }}
}}"); }}");

View File

@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -16,27 +17,26 @@ namespace EntityFrameworkCore.Projections.Infrastructure.Internal
Info = new ExtensionInfo(this); Info = new ExtensionInfo(this);
} }
public DbContextOptionsExtensionInfo Info { get; } public DbContextOptionsExtensionInfo Info { get; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Needed")]
public void ApplyServices(IServiceCollection services) public void ApplyServices(IServiceCollection services)
{ {
var existingPreprocessorFactoryRegistration = services.FirstOrDefault(x => x.ServiceType == typeof(IQueryTranslationPreprocessorFactory)); var queryCompilerRegistration = services.FirstOrDefault(x => x.ServiceType == typeof(IQueryCompiler));
if (queryCompilerRegistration?.ImplementationType is null)
if (existingPreprocessorFactoryRegistration?.ImplementationType is null)
{ {
throw new InvalidOperationException("Expected a QueryTranslationPreprocessor to be registered. Please make sure to register your database provider first"); throw new InvalidOperationException("No queryCompiler is configured yet. Please make sure to configure a database provider first"); ;
} }
// Ensure that we can still resolve this factory // Ensure that we can still resolve this queryCompiler
services.Add(new ServiceDescriptor(existingPreprocessorFactoryRegistration.ImplementationType, existingPreprocessorFactoryRegistration.ImplementationType, existingPreprocessorFactoryRegistration.Lifetime)); services.Add(new ServiceDescriptor(queryCompilerRegistration.ImplementationType, queryCompilerRegistration.ImplementationType, queryCompilerRegistration.Lifetime));
services.Remove(existingPreprocessorFactoryRegistration); services.Remove(queryCompilerRegistration);
services.Add(new ServiceDescriptor( services.Add(new ServiceDescriptor(
typeof(IQueryTranslationPreprocessorFactory), typeof(IQueryCompiler),
serviceProvider => new WrappedQueryTranslationPreprocessorFactory((IQueryTranslationPreprocessorFactory)serviceProvider.GetRequiredService(existingPreprocessorFactoryRegistration.ImplementationType), serviceProvider.GetRequiredService<QueryTranslationPreprocessorDependencies>()), serviceProvider => new WrappedQueryCompiler((IQueryCompiler)serviceProvider.GetRequiredService(queryCompilerRegistration.ImplementationType)),
existingPreprocessorFactoryRegistration.Lifetime queryCompilerRegistration.Lifetime
)); ));
} }
public void Validate(IDbContextOptions options) public void Validate(IDbContextOptions options)

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using EntityFrameworkCore.Projections.Services;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
namespace EntityFrameworkCore.Projections.Infrastructure.Internal
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Needed")]
public sealed class WrappedQueryCompiler : IQueryCompiler
{
readonly ProjectableExpressionReplacer _projectionExpressionReplacer = new();
readonly IQueryCompiler _queryCompiler;
public WrappedQueryCompiler(IQueryCompiler queryCompiler)
{
_queryCompiler = queryCompiler;
}
public Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query)
=> _queryCompiler.CreateCompiledAsyncQuery<TResult>(PatchQuery(query));
public Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query)
=> _queryCompiler.CreateCompiledQuery<TResult>(PatchQuery(query));
public TResult Execute<TResult>(Expression query)
=> _queryCompiler.Execute<TResult>(PatchQuery(query));
public TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancellationToken)
=> _queryCompiler.ExecuteAsync<TResult>(PatchQuery(query), cancellationToken);
Expression PatchQuery(Expression expression)
=> _projectionExpressionReplacer.Visit(expression);
}
}

View File

@@ -1,54 +0,0 @@
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);
}
}
}

View File

@@ -0,0 +1,28 @@
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 sealed class ParameterExtractor : ExpressionVisitor
{
List<ParameterExpression>? _extractedParameters = null;
protected override Expression VisitParameter(ParameterExpression node)
{
if (_extractedParameters is null)
{
_extractedParameters = new List<ParameterExpression>();
}
_extractedParameters.Add(node);
return base.VisitParameter(node);
}
public IEnumerable<ParameterExpression> ExtractedParameters
=> _extractedParameters ?? Enumerable.Empty<ParameterExpression>();
}
}

View File

@@ -43,17 +43,22 @@ namespace EntityFrameworkCore.Projections.Services
} }
else else
{ {
var test1 = argumentExpressions.Cast<ParameterExpression>()!; var parameterExtractor = new ParameterExtractor();
foreach (var argument in argumentExpressions)
{
parameterExtractor.Visit(argument);
}
var expressionFactoryConstructionMethod = var lambda = Expression.Lambda<Func<LambdaExpression>>(
Expression.Lambda<Func<LambdaExpression>>(
Expression.Call( Expression.Call(
expressionFactoryMethod, expressionFactoryMethod,
argumentExpressions argumentExpressions
) ),
).Compile(); parameterExtractor.ExtractedParameters
);
return expressionFactoryConstructionMethod.Invoke();
return lambda.Compile().Invoke();
} }
}); });
}); });

View File

@@ -0,0 +1,11 @@
namespace EntityFrameworkCore.Projections.FunctionalTests.ExtensionMethods
{
public partial class ExtensionMethodTests
{
public class Entity
{
public int Id { get; set; }
}
}
}

View File

@@ -0,0 +1,8 @@
namespace EntityFrameworkCore.Projections.FunctionalTests.ExtensionMethods
{
public static class EntityExtensions
{
[Projectable]
public static int Foo(this ExtensionMethodTests.Entity entity) => entity.Id + 1;
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EntityFrameworkCore.Projections.FunctionalTests.Helpers;
using Microsoft.EntityFrameworkCore;
using ScenarioTests;
using Xunit;
namespace EntityFrameworkCore.Projections.FunctionalTests.ExtensionMethods
{
public partial class ExtensionMethodTests
{
[Scenario(NamingPolicy = ScenarioTestMethodNamingPolicy.Test)]
public void PlayScenario(ScenarioContext scenario)
{
using var dbContext = new SampleDbContext<Entity>();
scenario.Fact("We can select on a projectable extension method", () => {
const string expectedQueryString = "SELECT [e].[Id]\r\nFROM [Entity] AS [e]";
var query = dbContext.Set<Entity>()
.Select(x => x.Foo());
Assert.Equal(expectedQueryString, query.ToQueryString());
});
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EntityFrameworkCore.Projections.FunctionalTests.Helpers;
using EntityFrameworkCore.Projections.Services;
using Microsoft.EntityFrameworkCore;
using ScenarioTests;
using Xunit;
namespace EntityFrameworkCore.Projections.FunctionalTests
{
public partial class StatefullComplexFunctionTests
{
public record Entity
{
public int Id { get; set; }
[Projectable]
public int Computed(int argument) => Id + argument;
}
[Scenario(NamingPolicy = ScenarioTestMethodNamingPolicy.Test)]
public void PlayScenario(ScenarioContext scenario)
{
// Setup
using var dbContext = new SampleDbContext<Entity>();
scenario.Fact("We can filter on a projectable property", () => {
const string expectedQueryString =
@"DECLARE @__argument_0 int = 1;
SELECT [e].[Id]
FROM [Entity] AS [e]
WHERE ([e].[Id] + @__argument_0) = 2";
var query = dbContext.Set<Entity>().AsQueryable()
.Where(x => x.Computed(1) == 2);
Assert.Equal(expectedQueryString, query.ToQueryString());
});
scenario.Fact("We can select on a projectable property", () => {
const string expectedQueryString =
@"DECLARE @__argument_0 int = 1;
SELECT [e].[Id] + @__argument_0
FROM [Entity] AS [e]";
var query = dbContext.Set<Entity>()
.AsQueryable()
.Select(x => x.Computed(1));
Assert.Equal(expectedQueryString, query.ToQueryString());
});
scenario.Fact("We can pass in variables", () => {
const string expectedQueryString =
@"DECLARE @__argument_0 int = 1;
SELECT [e].[Id] + @__argument_0
FROM [Entity] AS [e]";
var argument = 1;
var query = dbContext.Set<Entity>()
.Select(x => x.Computed(argument));
Assert.Equal(expectedQueryString, query.ToQueryString());
});
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using EntityFrameworkCore.Projections.FunctionalTests.Helpers; using EntityFrameworkCore.Projections.FunctionalTests.Helpers;
using EntityFrameworkCore.Projections.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using ScenarioTests; using ScenarioTests;
using Xunit; using Xunit;
@@ -28,25 +29,39 @@ namespace EntityFrameworkCore.Projections.FunctionalTests
using var dbContext = new SampleDbContext<Entity>(); using var dbContext = new SampleDbContext<Entity>();
scenario.Fact("We can filter on a projectable property", () => { scenario.Fact("We can filter on a projectable property", () => {
const string expectedQueryString = "SELECT [e].[Id]\r\nFROM [Entity] AS [e]\r\nWHERE 0 = 1"; const string expectedQueryString =
@"DECLARE @__p_0 bit = CAST(0 AS bit);
var query = dbContext.Set<Entity>() SELECT [e].[Id]
FROM [Entity] AS [e]
WHERE @__p_0 = CAST(1 AS bit)";
var query = dbContext.Set<Entity>().AsQueryable()
.Where(x => x.Computed(0) == 1); .Where(x => x.Computed(0) == 1);
Assert.Equal(expectedQueryString, query.ToQueryString()); Assert.Equal(expectedQueryString, query.ToQueryString());
}); });
scenario.Fact("We can select on a projectable property", () => { scenario.Fact("We can select on a projectable property", () => {
const string expectedQueryString = "SELECT 0\r\nFROM [Entity] AS [e]"; const string expectedQueryString =
@"DECLARE @__argument1_0 int = 0;
SELECT @__argument1_0
FROM [Entity] AS [e]";
var query = dbContext.Set<Entity>() var query = dbContext.Set<Entity>()
.AsQueryable()
.Select(x => x.Computed(0)); .Select(x => x.Computed(0));
Assert.Equal(expectedQueryString, query.ToQueryString()); Assert.Equal(expectedQueryString, query.ToQueryString());
}); });
scenario.Fact("We can pass in variables", () => { scenario.Fact("We can pass in variables", () => {
const string expectedQueryString = "SELECT 0\r\nFROM [Entity] AS [e]"; const string expectedQueryString =
@"DECLARE @__argument1_0 int = 0;
SELECT @__argument1_0
FROM [Entity] AS [e]";
var argument = 0; var argument = 0;
var query = dbContext.Set<Entity>() var query = dbContext.Set<Entity>()

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -0,0 +1,14 @@
using System;
using System.Linq;
using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated
#nullable disable
{
public static class Foo_C_Foo
{
public static System.Linq.Expressions.Expression<System.Func<Foo.D, int>> Expression(D d) =>
projectionTarget => 1;
}
}

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -1,5 +1,6 @@
using System; using System;
using EntityFrameworkCore.Projections; using EntityFrameworkCore.Projections;
using Foo;
namespace EntityFrameworkCore.Projections.Generated namespace EntityFrameworkCore.Projections.Generated
#nullable disable #nullable disable

View File

@@ -292,6 +292,31 @@ namespace Foo {
return Verifier.Verify(result.GeneratedTrees[0].ToString()); return Verifier.Verify(result.GeneratedTrees[0].ToString());
} }
[Fact]
public Task ProjectableExtensionMethod()
{
var compilation = CreateCompilation(@"
using System;
using System.Linq;
using EntityFrameworkCore.Projections;
namespace Foo {
class D { }
static class C {
[Projectable]
public static int Foo(this D d) => 1;
}
}
");
var result = RunGenerator(compilation);
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedTrees);
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}
#region Helpers #region Helpers