dotnet / dotnet/efcore

Checking equality of values with convertible types makes query precompilation fail

Open
#38,012 1 comment 0 reactions 0 assignees View on GitHub
area-precompiled-queries customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### Bug description

`Blog` entity has a property with a custom type `Name` for which a type converter to `string` is configured. There is a query which takes a `string` parameter and checks if it's equal to the property of type `Name`:

```
private static Blog? Query(string name)
{
using var context = new Context();
var localName = name;
// var localName = new Name(name);
return context.Blogs.FirstOrDefault(blog => blog.Name == localName);
}
```

Precompilation for this query fails with an error:

```
QueryPrecompilationError { SyntaxNode = context.Blogs.FirstOrDefault(blog => blog.Name == localName), Exception = System.InvalidOperationException: The binary operator Equal is not defined for the types 'Name' and 'System.String'.
```

When parameter is converted from `string` to `Name` beforehand, query precompilation succeeds.

When project is compiled without query precompilation, query runs without issues.

See attached project with minimal example.

Can be reproduced with:

```
> dotnet ef dbcontext optimize --output-dir Generated --precompile-queries --nativeaot --verbose
```

[valtype.zip](https://github.com/user-attachments/files/26279194/valtype.zip)

### Your code

```csharp
--- Model.cs

public class Blog
{
public int Id { get; set; }

public required Name Name { get; set; }
}

public class Name
{
private readonly string value;

public Name(string value)
{
this.value = value;
}

public static implicit operator string(Name name) => name.value;
}

--- Context.cs

using Microsoft.EntityFrameworkCore;

public class Context : DbContext
{
public DbSet Blogs { get; set; } = null!;

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("Data Source=tmp.db");
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity()
.Property(b => b.Name)
.HasConversion(name => (string)name, name => new Name(name));
}
}

--- Program.cs

using Microsoft.EntityFrameworkCore;

public static class Program
{
public static async Task Main(string[] args)
{
_ = Query("Some name");
return 0;
}

private static Blog? Query(string name)
{
using var context = new Context();
var localName = name;
// var localName = new Name(name);
return context.Blogs.FirstOrDefault(blog => blog.Name == localName);
}
}
```

### Stack traces

```text

```

### Verbose output

```text
> dotnet ef dbcontext optimize --output-dir Generated --nativeaot --precompile-queries --verbose
Using project '/workspaces/ef-play/valtype/valtype.csproj'.
Using startup project '/workspaces/ef-play/valtype/valtype.csproj'.
dotnet msbuild /getProperty:ProjectName /getProperty:AssemblyName /getProperty:DesignAssembly /getProperty:Language /getProperty:OutputPath /getProperty:PlatformTarget /getProperty:ProjectAssetsFile /getProperty:ProjectDir /getProperty:RootNamespace /getProperty:RuntimeFrameworkVersion /getProperty:TargetFileName /getProperty:TargetFrameworkMoniker /getProperty:Nullable /getProperty:TargetFramework /getProperty:TargetPlatformIdentifier /getProperty:Platform /t:ResolvePackageAssets /getItem:RuntimeCopyLocalItems /workspaces/ef-play/valtype/valtype.csproj
dotnet msbuild /getProperty:ProjectName /getProperty:AssemblyName /getProperty:DesignAssembly /getProperty:Language /getProperty:OutputPath /getProperty:PlatformTarget /getProperty:ProjectAssetsFile /getProperty:ProjectDir /getProperty:RootNamespace /getProperty:RuntimeFrameworkVersion /getProperty:TargetFileName /getProperty:TargetFrameworkMoniker /getProperty:Nullable /getProperty:TargetFramework /getProperty:TargetPlatformIdentifier /getProperty:Platform /t:ResolvePackageAssets /getItem:RuntimeCopyLocalItems /workspaces/ef-play/valtype/valtype.csproj
Build started...
dotnet build /workspaces/ef-play/valtype/valtype.csproj /verbosity:quiet /nologo /p:PublishAot=false /p:EFOptimizeContext=false

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:04.80

Build succeeded.
dotnet exec --depsfile /workspaces/ef-play/valtype/bin/Debug/net11.0/valtype.deps.json --additionalprobingpath /root/.nuget/packages --runtimeconfig /workspaces/ef-play/valtype/bin/Debug/net11.0/valtype.runtimeconfig.json /root/.dotnet/tools/.store/dotnet-ef/11.0.0-preview.1.26104.118/dotnet-ef/11.0.0-preview.1.26104.118/tools/net10.0/any/tools/net10.0/any/ef.dll dbcontext optimize --output-dir Generated --nativeaot --precompile-queries --assembly /workspaces/ef-play/valtype/bin/Debug/net11.0/valtype.dll --project /workspaces/ef-play/valtype/valtype.csproj --startup-assembly /workspaces/ef-play/valtype/bin/Debug/net11.0/valtype.dll --startup-project /workspaces/ef-play/valtype/valtype.csproj --project-dir /workspaces/ef-play/valtype/ --root-namespace valtype --language C# --framework net11.0 --design-assembly /root/.nuget/packages/microsoft.entityframeworkcore.design/11.0.0-preview.2.26159.112/lib/net11.0/Microsoft.EntityFrameworkCore.Design.dll --nullable --working-dir /workspaces/ef-play/valtype --verbose
Query precompilation is an experimental feature and should be used with caution.
NativeAOT support is experimental and can change in the future.
Using assembly 'valtype'.
Using startup assembly 'valtype'.
Using application base '/workspaces/ef-play/valtype/bin/Debug/net11.0'.
Using working directory '/workspaces/ef-play/valtype'.
Using root namespace 'valtype'.
Using project directory '/workspaces/ef-play/valtype/'.
Remaining arguments: .
Finding DbContext classes...
Using environment 'Development'.
Finding IDesignTimeDbContextFactory implementations...
Finding DbContext classes in the project...
Found DbContext 'Context'.
Finding application service provider in assembly 'valtype'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Using context 'Context'.
Finding design-time services referenced by assembly 'valtype'...
Finding design-time services referenced by assembly 'valtype'...
No referenced design-time services were found.
Finding design-time services for provider 'Microsoft.EntityFrameworkCore.Sqlite'...
Using design-time services from provider 'Microsoft.EntityFrameworkCore.Sqlite'.
Finding IDesignTimeServices implementations in assembly 'valtype'...
No design-time services were found.
Successfully generated a compiled model, it will be discovered automatically, but you can also call 'options.UseModel(ContextModel.Instance)'. Run this command again when the model is modified.
'Context' disposed.
System.InvalidOperationException: Query precompilation failed with errors:
QueryPrecompilationError { SyntaxNode = context.Blogs.FirstOrDefault(blog => blog.Name == localName), Exception = System.InvalidOperationException: The binary operator Equal is not defined for the types 'Name' and 'System.String'.
at System.Linq.Expressions.Expression.GetEqualityComparisonOperator(ExpressionType binaryType, String opName, Expression left, Expression right, Boolean liftToNull)
at System.Linq.Expressions.Expression.Equal(Expression left, Expression right, Boolean liftToNull, MethodInfo method)
at System.Linq.Expressions.Expression.Equal(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitBinaryExpression(BinaryExpressionSyntax binary)
at Microsoft.CodeAnalysis.CSharp.Syntax.BinaryExpressionSyntax.Accept[TResult](CSharpSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitLambdaExpression(AnonymousFunctionExpressionSyntax lambda, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitArgument(ArgumentSyntax argument, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitInvocationExpression(InvocationExpressionSyntax invocation)
at Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax.Accept[TResult](CSharpSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Translate(SyntaxNode node, SemanticModel semanticModel)
at Microsoft.EntityFrameworkCore.Query.Internal.PrecompiledQueryCodeGenerator.ProcessSyntaxTree(SyntaxTree syntaxTree, SemanticModel semanticModel, IReadOnlyList`1 locatedQueries, List`1 precompilationErrors, String suffix, ISet`1 generatedFileNames, CancellationToken cancellationToken) }

at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.PrecompileQueries(String outputDir, DbContext context, String suffix, IServiceProvider services, IReadOnlyDictionary`2 memberAccessReplacements, ISet`1 generatedFileNames)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.Optimize(String outputDir, String modelNamespace, String suffix, Boolean scaffoldModel, Boolean precompileQueries, DbContext context, Boolean optimizeAllInAssembly, Boolean nativeAot, List`1 generatedFiles, HashSet`1 generatedFileNames)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.Optimize(String outputDir, String modelNamespace, String contextTypeName, String suffix, Boolean scaffoldModel, Boolean precompileQueries, Boolean nativeAot)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OptimizeContextImpl(String outputDir, String modelNamespace, String contextType, String suffix, Boolean scaffoldModel, Boolean precompileQueries, Boolean nativeAot)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OptimizeContext.<>c__DisplayClass0_0.<.ctor>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Query precompilation failed with errors:
QueryPrecompilationError { SyntaxNode = context.Blogs.FirstOrDefault(blog => blog.Name == localName), Exception = System.InvalidOperationException: The binary operator Equal is not defined for the types 'Name' and 'System.String'.
at System.Linq.Expressions.Expression.GetEqualityComparisonOperator(ExpressionType binaryType, String opName, Expression left, Expression right, Boolean liftToNull)
at System.Linq.Expressions.Expression.Equal(Expression left, Expression right, Boolean liftToNull, MethodInfo method)
at System.Linq.Expressions.Expression.Equal(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitBinaryExpression(BinaryExpressionSyntax binary)
at Microsoft.CodeAnalysis.CSharp.Syntax.BinaryExpressionSyntax.Accept[TResult](CSharpSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitLambdaExpression(AnonymousFunctionExpressionSyntax lambda, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitArgument(ArgumentSyntax argument, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node, Type expectedType)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.VisitInvocationExpression(InvocationExpressionSyntax invocation)
at Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax.Accept[TResult](CSharpSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor`1.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Visit(SyntaxNode node)
at Microsoft.EntityFrameworkCore.Query.Internal.CSharpToLinqTranslator.Translate(SyntaxNode node, SemanticModel semanticModel)
at Microsoft.EntityFrameworkCore.Query.Internal.PrecompiledQueryCodeGenerator.ProcessSyntaxTree(SyntaxTree syntaxTree, SemanticModel semanticModel, IReadOnlyList`1 locatedQueries, List`1 precompilationErrors, String suffix, ISet`1 generatedFileNames, CancellationToken cancellationToken) }
```

### EF Core version

10.0.3, 11.0.0-preview.2.26159.112

### Database provider

Microsoft.EntityFrameworkCore.Sqlite

### Target framework

.NET 10, .NET 11

### Operating system

Ubuntu 24.04.4 LTS

### IDE

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.