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

The binary operator Equal is not defined for the types 'System.ValueTuple2[System.Net.IPAddress,System.Int32]' and 'System.ValueTuple2[System.Net.IPAddress,System.Int32]' #2458

Closed
ioaznnis opened this issue Jul 31, 2022 · 8 comments

Comments

@ioaznnis
Copy link

ioaznnis commented Jul 31, 2022

When use EF.Functions.Network for work with cidr, i get error when try contains operator in queryable: System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.ValueTuple2[System.Net.IPAddress,System.Int32]' and 'System.ValueTuple2[System.Net.IPAddress,System.Int32]'..

This worked with EF Core 3.1. However after upgrading to EF Core 6, we get exception.

Test case

    const int subnet = 16;
    
    var queryable = dbContext.Entities
        .Select(x => EF.Functions.Network(EF.Functions.SetMaskLength(x.IpAddress, subnet)));

    var count = await queryable
        .Where(x=>queryable.Contains(x))
        .ToListAsync();
public class Entity
{
    public Guid Id { get; set; }
    
    public IPAddress IpAddress { get; set; } = null!;
}
public class ExampleDbContext : DbContext
{
    public ExampleDbContext(DbContextOptions<ExampleDbContext> options) : base(options)
    {
    }

    public DbSet<Entity> Entities { get; set; } = null!;
}

Full Example

Expected SQL

SELECT network(set_masklen(e."IpAddress", 16))
      FROM "Entities" AS e
      WHERE network(set_masklen(e."IpAddress", 16)) IN (
          SELECT network(set_masklen(e0."IpAddress", 16))
          FROM "Entities" AS e0
      )

Exception

Full Exception
System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]' and 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]'.
   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 Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitLambda[T](Expression`1 lambdaExpression)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitUnary(UnaryExpression unaryExpression)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

Include provider and version information

Runtime: 6.0.7
EF core: 6.0.5
Npgsql: 6.0.5

@roji
Copy link
Member

roji commented Aug 1, 2022

Thanks, I can see the error happening. This is a tricky one.

For the following LINQ query:

_ = ctx.Entities
    .Where(x => ctx.Entities.Select(e => e.Name).Contains(x.Name))
    .ToList();

... EF Core 3 translates to the following SQL:

SELECT e."Id", e."IpAddress", e."Name"
        FROM "Entities" AS e
        WHERE e."Name" IN (
            SELECT e0."Name"
            FROM "Entities" AS e0
        )

... while EF Core 6/7 translate to the following:

SELECT e."Id", e."IpAddress", e."Name"
        FROM "Entities" AS e
        WHERE EXISTS (
            SELECT 1
            FROM "Entities" AS e0
            WHERE e0."Name" = e."Name" OR ((e0."Name" IS NULL) AND (e."Name" IS NULL)))

Now, this works well when the thing being compared is a simple string. When it's a value tuple, you get the exception since value tuples aren't really supported within expression trees yet (dotnet/roslyn#12897); concretely, trying to construct an Equal expression over two value tuples (as the above does) throws.

I do have a possible hacky workaround though. If, instead of converting your inet to a cidr, you simply extract the broadcast address, you can compare broadcast addresses without passing through a cidr value tuple:

var queryable = ctx.Entities
    .Select(x => EF.Functions.Broadcast(EF.Functions.SetMaskLength(x.IpAddress, subnet)));

var count = await queryable
    .Where(x => queryable.Contains(x))
    .ToListAsync();

This should yield the same results as comparing cidrs.

/cc @smitpatel @AndriySvyryd @ajcvickers

@ioaznnis
Copy link
Author

ioaznnis commented Aug 1, 2022

Thank, as a workaround, it's better used EF.Functions.Broadcast than found EF.Functions.Host.

Will wait resolwe dotnet/roslyn#12897

@roji
Copy link
Member

roji commented Aug 1, 2022

Closing as there's nothing to be done on the EFCore.PG side.

@roji roji closed this as not planned Won't fix, can't repro, duplicate, stale Aug 1, 2022
@smitpatel
Copy link

(If we care this is important), need to investigate more. The exception stack trace throws from ProcessJoin in nav expansion but the query doesn't have any joins at all. So repro and exception doesn't match.

The query posted by @roji if that is throwing then we may be able to be more resilient and generate object.Equals method which makes it work.

@ioaznnis
Copy link
Author

ioaznnis commented Aug 1, 2022

@smitpatel, sorry, exception from another problem, with Join by ValueTuple as key.
This not working in 3.1 also.

Correct full Exception
System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]' and 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]'.
   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 Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitLambda[T](Expression`1 lambdaExpression)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitUnary(UnaryExpression unaryExpression)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

@smitpatel
Copy link

@roji, would you be able to add regression test for this in efcore.pg? Is there a way to write test in efcore repo? I have fix for this in efcore.

smitpatel added a commit to dotnet/efcore that referenced this issue Aug 5, 2022
…ng comparison in query

Not all types implement equality operator, so it can throw exception.
Reference types, Nullable<T> & known types are ok to be used with Expression.Equal

Originally reported in npgsql/efcore.pg#2458
ghost pushed a commit to dotnet/efcore that referenced this issue Aug 6, 2022
…ng comparison in query (#28608)

Not all types implement equality operator, so it can throw exception.
Reference types, Nullable<T> & known types are ok to be used with Expression.Equal

Originally reported in npgsql/efcore.pg#2458
@roji
Copy link
Member

roji commented Aug 6, 2022

Sure, I can add a test here. To add a test in EF Core... Can we use InMemory to test for this (i.e. "persist" a value tuple and query it back)?

@roji
Copy link
Member

roji commented Aug 6, 2022

But of course you want to test the relational code too... Can it be tested with a value converter maybe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants