Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot generate compiled model on EFCore.PG with an array value converter #32659

Open
JustArchi opened this issue Dec 21, 2023 · 4 comments
Open

Comments

@JustArchi
Copy link

JustArchi commented Dec 21, 2023

File a bug

Hey, the below worked fine in EF Core 7 (.NET 7). After update to 8.0.0, it now gives error during dotnet ef dbcontext optimize. Naturally I stumbled upon this in my own real project, but I've extracted minimal reproducible case that should make you run into the issue.

Include your code

[Table("test")]
public sealed class Asset {
	[Column]
	[Key]
	public int ID { get; set; }
	
	[Column]
	public List<EType> MatchableTypes { get; set; } = new();
}

public enum EType {
	Unknown,
	One,
	Two
}

public sealed class MyContext : DbContext {
	public DbSet<Asset> Assets => Set<Asset>();

	public MyContext(DbContextOptions<MyContext> options) : base(options) { }
}

Include stack traces

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
   at Microsoft.CodeAnalysis.CSharp.SyntaxFactory.GetBinaryExpressionOperatorTokenKind(SyntaxKind kind)
   at Microsoft.CodeAnalysis.CSharp.SyntaxFactory.BinaryExpression(SyntaxKind kind, ExpressionSyntax left, ExpressionSyntax right)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitBinary(BinaryExpression binary)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitBlock(BlockExpression block)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.<TranslateConditional>g__TranslateConditionalStatement|35_0(ConditionalExpression conditional, <>c__DisplayClass35_0&)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.TranslateConditional(ConditionalExpression conditional, IdentifierNameSyntax lowerableAssignmentVariable)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitConditional(ConditionalExpression conditional)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitLoop(LoopExpression loop)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitBlock(BlockExpression block)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitLoop(LoopExpression loop)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitBlock(BlockExpression block)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitBlock(BlockExpression block)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.TranslateConditional(ConditionalExpression conditional, IdentifierNameSyntax lowerableAssignmentVariable)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitConditional(ConditionalExpression conditional)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Translate(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.VisitLambda[T](Expression`1 lambda)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.TranslateCore(Expression node, ISet`1 collectedNamespaces, Boolean statementContext)
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqToCSharpSyntaxTranslator.TranslateExpression(Expression node, ISet`1 collectedNamespaces)
   at Microsoft.EntityFrameworkCore.Design.Internal.CSharpHelper.Expression(Expression node, ISet`1 collectedNamespaces)
   at Microsoft.EntityFrameworkCore.Design.Internal.CSharpRuntimeAnnotationCodeGenerator.Create(ValueConverter converter, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, ICSharpHelper codeHelper)
   at Microsoft.EntityFrameworkCore.Design.Internal.RelationalCSharpRuntimeAnnotationCodeGenerator.Create(CoreTypeMapping typeMapping, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, ValueComparer valueComparer, ValueComparer keyValueComparer, ValueComparer providerValueComparer)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Design.Internal.NpgsqlCSharpRuntimeAnnotationCodeGenerator.Create(CoreTypeMapping typeMapping, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, ValueComparer valueComparer, ValueComparer keyValueComparer, ValueComparer providerValueComparer)
   at Microsoft.EntityFrameworkCore.Design.Internal.ICSharpRuntimeAnnotationCodeGenerator.Create(CoreTypeMapping typeMapping, IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
   at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.Create(IProperty property, String variableName, Dictionary`2 propertyVariables, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
   at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.Create(IProperty property, Dictionary`2 propertyVariables, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
   at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.CreateEntityType(IEntityType entityType, IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, SortedSet`1 namespaces, String className, Boolean nullable)
   at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.GenerateEntityType(IEntityType entityType, String namespace, String className, Boolean nullable)
   at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.GenerateModel(IModel model, CompiledModelCodeGenerationOptions options)
   at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CompiledModelScaffolder.ScaffoldModel(IModel model, String outputDir, CompiledModelCodeGenerationOptions options)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.Optimize(String outputDir, String modelNamespace, String contextTypeName)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OptimizeContextImpl(String outputDir, String modelNamespace, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OptimizeContext.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)

Include provider and version information

EF Core version: 8.0.0
Database provider: Npgsql.EntityFrameworkCore.PostgreSQL
Target framework: .NET 8.0
Operating system: Linux Debian Testing
IDE: JetBrains Rider 2023.3.2

Originally reported in npgsql/efcore.pg#2976

@JustArchi JustArchi changed the title .NET 8 fails generating optimized model when using List<TEnum> .NET 8 fails generating optimized model when using List<TEnum> Dec 21, 2023
@XieJJ99
Copy link

XieJJ99 commented Dec 22, 2023

We're facing the same issue, the model works without pre-compiled model, but throws the "System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values." exception when trying to generate the optimized model.
We attempted the following solutions without success:

Marking the property as PrimitiveCollection
Marking the property as PrimitiveCollection and then set the element conversion to int

@ajcvickers
Copy link
Contributor

Note for triage: this is a regression on the Npgsql provider from 7 to 8. Does not repro with SQL Server. /cc @roji

@roji roji self-assigned this Jan 4, 2024
@roji roji added the type-bug label Jan 4, 2024
@roji roji added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jan 4, 2024
@roji
Copy link
Member

roji commented Jan 4, 2024

This indeed turned out to be incorrect handling of compound assignment operators in LinqToCSharpSyntaxTranslator (anything like +=, *=). Submitted #32715 to fix that.

Once that's fixed, the translator still fails on another error. The incoming LINQ tree is as follows:

value => value == null ? null :
{
    int[] result;
    int length;
    int i;
    length = value.Count;
    result = new int[length];
    {
        i = 0;
        Loop(Break: LoopBreak Continue: )
        {
            i < length ?
            {
                result[i] = Invoke(value => (int)value, value[i]);
                return (i += 1)
                ;
            } : Goto(break LoopBreak)
        }
    }
    return result;
}

This code is from NpgsqlArrayConverter; this is a composing value converter that knows how to convert between arrays given an element value converter (this is why this issue is PG-only).

It's not really visible in the code above, but the expression produces by the converter has two nested blocks, each declaring the variable i, effectively as follows:

var i = 0;
{
    var i = 8;
}

Although this works in LINQ, this is of course illegal in C# and causes LinqToCSharpSyntaxTranslator to bomb (the error message should be improved, #32716). Once the unneeded outer declaration is removed (npgsql/efcore.pg#3047), the compiled model is generated successfully.

@roji roji changed the title .NET 8 fails generating optimized model when using List<TEnum> Cannot generate compiled model on EFCore.PG with an array value converter Jan 4, 2024
@roji roji added Servicing-consider closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. and removed closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. Servicing-consider labels Jan 4, 2024
@roji
Copy link
Member

roji commented Jan 4, 2024

While after these fixes the compiled model can be generated, it is invalid since NpgsqlArrayConverter produces statement/loop nodes, but these are invalid in the Expression<Func<...>> context where the tree is required. Opened #32717 to track/discuss.

Accordingly, am removing servicing-consider from this... In any case, the compound assignment problem tracked by this issue is trivial to work around in Npgsql code, so we don't really need to patch it.

@roji roji added this to the 9.0.0 milestone Jan 4, 2024
@roji roji added needs-design and removed closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. labels Jan 9, 2024
@roji roji removed this from the 9.0.0 milestone Jan 9, 2024
@ajcvickers ajcvickers added this to the Backlog milestone Jan 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants