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

Value Conversion of custom struct type throws exception if it doesn't implement == and != operators #12290

Closed
gojanpaolo opened this issue Jun 7, 2018 · 7 comments
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@gojanpaolo
Copy link

gojanpaolo commented Jun 7, 2018

Exception message: 

The binary operator Equal is not defined for the types 'ExceptionEqualIsNotDefined.Fuel' and 'ExceptionEqualIsNotDefined.Fuel'.

Stack trace:

   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrAccessorFactory`1.Create(PropertyInfo propertyInfo, IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrAccessorFactory`1.Create(IPropertyBase property)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyBase.<>c.<get_Getter>b__34_0(PropertyBase p)
   at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyBase.get_Getter()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.ReadPropertyValue(IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
   at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges()
   at Microsoft.EntityFrameworkCore.DbContext.TryDetectChanges()
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at ExceptionEqualIsNotDefined.Tests.Test() in 

Steps to reproduce

public class Tests
{
    [Fact]
    public void Test()
    {
        using (var connection = new SqliteConnection("DataSource=:memory:"))
        {
            connection.Open();

            var options = new DbContextOptionsBuilder<Context>()
                .UseSqlite(connection)
                .Options;

            using (var context = new Context(options))
            {
                context.Database.EnsureCreated();
            }

            using (var context = new Context(options))
            {
                context.Add(new Load());
                context.SaveChanges(); //throws exception here
            }
        }
    }
}

public class Context : DbContext
{
    public Context(DbContextOptions options) : base(options) { }

    public DbSet<Load> Load { get; private set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Load>(e =>
        {
            e.Property(l => l.Fuel)
                .HasConversion(
                    f => f.Volume,
                    v => new Fuel(v));
        });
    }
}

public class Load
{
    public int LoadId { get; private set; }

    public Fuel Fuel { get; set; }
}

public struct Fuel
{
    public Fuel(double volume)
    {
        Volume = volume;
    }

    public double Volume { get; }

    // Uncomment below to get rid of exception.
    //public static bool operator ==(Fuel left, Fuel right)
    //{
    //    return left.Volume == right.Volume;
    //}

    //public static bool operator !=(Fuel left, Fuel right)
    //{
    //    return left.Volume != right.Volume;
    //}

    //// Need to override Equals and GetHashCode to get rid of warning.
    //public override bool Equals(object obj)
    //{
    //    if (obj == null || GetType() != obj.GetType())
    //    {
    //        return false;
    //    }

    //    return Volume.Equals(((Fuel)obj).Volume);
    //}

    //public override int GetHashCode()
    //{
    //    return Volume.GetHashCode();
    //}
}

Further technical details

EF Core version: 2.1.0
Database Provider: Microsoft.EntityFrameworkCore.Sqlite
Operating system: Win7
IDE: Visual Studio 2017 15.7.2

@gojanpaolo gojanpaolo changed the title Value Conversion of custom struct type throws exception if doesn't implement == and != operators Value Conversion of custom struct type throws exception if it doesn't implement == and != operators Jun 7, 2018
@ajcvickers
Copy link
Member

Note for triage: I was able to repro this.

@ajcvickers ajcvickers self-assigned this Jun 8, 2018
@ajcvickers ajcvickers added this to the 2.1.3 milestone Jun 8, 2018
@smitpatel
Copy link
Contributor

Structs cannot use operators == & != unless implemented https://stackoverflow.com/questions/15199026/comparing-two-structs-using

ajcvickers added a commit that referenced this issue Jun 15, 2018
Fixes #12290

The issue here is that a ValueComparer was not being used when it should have been.
@ajcvickers ajcvickers added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. Servicing-consider patch-approved and removed Servicing-consider labels Jun 15, 2018
@ajcvickers
Copy link
Member

This is approved for 2.1.3. Do not merge yet; branch is expected to open Monday.

Given these issues have been parked for some time, please be careful to ensure the correct commits get into the correct branches.

@ajcvickers
Copy link
Member

@ajcvickers This issue is approved for patch and the release\2.1 branch is now open for merging. Please ensure:

  • The appropriate changes get into both the release and dev branches
  • Quirking is included for the release branch only

@theNETTechie
Copy link

Getting the same issue with .NET Core 3.0 and EF Core 3.0. Resolution is to implement operators == & != on the struct.

Stack trace:
System.AggregateException: One or more errors occurred. (Exception has been thrown by the target of an invocation.)
 ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.InvalidOperationException: The binary operator Equal is not defined for the types 'Entities.Inputs.Input1[System.Decimal]' and 'Entities.Inputs.Input1[System.Decimal]'.
   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.Metadata.Internal.PropertyAccessorsFactory.CreateCurrentValueGetter[TProperty](IPropertyBase propertyBase, Boolean useStoreGeneratedValues)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.CreateGeneric[TProperty](IPropertyBase propertyBase)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.Create(IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyBase.<>c.<get_Accessors>b__39_0(PropertyBase p)
   at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func2 valueFactory)    at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyBase.get_Accessors()    at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyBaseExtensions.GetPropertyAccessors(IPropertyBase propertyBase)    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.GetCurrentValue[TProperty](IPropertyBase propertyBase)    at lambda_method(Closure , InternalEntityEntry )    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SidecarValues..ctor(InternalEntityEntry entry)    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.EnsureTemporaryValues()    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean isMaterialization, Boolean setModified, Boolean isCascadeDelete, CurrentValueType valueType)    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetTemporaryValue(IProperty property, Object value, Boolean setModified)    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ValueGenerationManager.Generate(InternalEntityEntry entry)    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable1 forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode1 node)    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode1 node, Func2 handleNode)    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)    at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)    at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)    at Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)    at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.Add(TEntity entity)

@QuantumToasted
Copy link

QuantumToasted commented Jun 22, 2020

It seems this issue still exists on MUCH newer versions. I'm running 5.0.0-preview.5.20278.2 and have just run into this issue.

System.InvalidOperationException: The binary operator Equal is not defined for the types 'Disqord.Snowflake' and 'Disqord.Snowflake'.
  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.Internal.EntityFinder`1.BuildPredicate(IReadOnlyList`1 keyProperties, ValueBuffer keyValues, ParameterExpression entityParameter)
  at Microsoft.EntityFrameworkCore.Internal.EntityFinder`1.BuildLambda(IReadOnlyList`1 keyProperties, ValueBuffer keyValues)
  at Microsoft.EntityFrameworkCore.Internal.EntityFinder`1.FindAsync(Object[] keyValues, CancellationToken cancellationToken)
  at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.FindAsync(Object[] keyValues)

It took a lot of googling but i eventually found this exact issue which seems to be related or maybe the same. Is the only current fix still to define == and !=? I can perhaps bother the developer of the library this struct is from to implement them, but I'd like to know if there is something else I could try. I did already try defining a ValueComparer<Snowflake> on models where it was appropriate, because I found another issue (don't have the link, sorry) that suggested it could solve the issue.

Is this worth re-opening or looking into again? Something seems up here, but it could just be my code.

@ajcvickers
Copy link
Member

@QuantumToasted Please open a new issue and attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
Development

No branches or pull requests

5 participants