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

SQLite: Exception when using enum on entity #29521

Closed
StringEpsilon opened this issue Nov 9, 2022 · 3 comments · Fixed by #29633
Closed

SQLite: Exception when using enum on entity #29521

StringEpsilon opened this issue Nov 9, 2022 · 3 comments · Fixed by #29633
Labels
area-sqlite 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

@StringEpsilon
Copy link

File a bug

When upgrading to efcore 7, the SQLite Database provider fails to save entities with enums. Instead, an Exception of type "System.InvalidOperationException" with a message like this is thrown: The binary operator Equal is not defined for the types 'System.Int64' and 'Profession'..

This is breaking one of my projects where I relied on int64 <-> Enum mapping for quite a few entities.

I have verified that the issue is not reproducible with Microsoft.EntityFrameworkCore.Sqlite version 6.0.10.
I have also verified that the issue is not present when using the InMemory Database provider (Microsoft.EntityFrameworkCore.InMemory, version 7.0.0).

Include your code

A full reproduction code can be found here:

https://github.com/StringEpsilon/efcore_regression/tree/main

I also have added branches that show that the issue does not exist on version 6.0.10 (https://github.com/StringEpsilon/efcore_regression/tree/efcore_6) and the InMemory provider (https://github.com/StringEpsilon/efcore_regression/tree/memory-db)

Minimal reproduction:

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

var dbContext = new TestContext();
dbContext.Database.EnsureCreated();

dbContext.Accounts.Add(new Account() { Profession = Profession.None});
dbContext.SaveChanges();

public partial class TestContext : DbContext
{
	public virtual DbSet<Account> Accounts { get; set; }

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

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<Account>(entity =>
		{
			entity.Property(e => e.Profession)
				.HasColumnType("integer")
				.HasColumnName("profession")
				.HasDefaultValueSql("'0'");
		}
		);
	}
}

public enum Profession : long {
	None = 0,
	GUARD = 1,
}

[Table("accounts")]
public partial class Account {
	[Key]
	[Column("id", TypeName = "integer")]
	public long Id { get; set; }

	public Profession Profession { get; set; }
}

Include stack traces

Unhandled exception. 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 'System.Int64' and 'Profession'.
   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.ChangeTracking.ValueComparer.DefaultValueComparer`1.ExtractEqualsBody(Expression leftExpression, Expression rightExpression)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.CreateCurrentValueGetter[TProperty](IPropertyBase propertyBase, Boolean useStoreGeneratedValues)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.CreateGeneric[TProperty](IPropertyBase propertyBase)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   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.RuntimePropertyBase.<>c.<Microsoft.EntityFrameworkCore.Metadata.Internal.IRuntimePropertyBase.get_Accessors>b__29_0(RuntimePropertyBase property)
   at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase.Microsoft.EntityFrameworkCore.Metadata.Internal.IRuntimePropertyBase.get_Accessors()
   at Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyBaseExtensions.GetPropertyAccessors(IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.GetCurrentValue[TProperty](IPropertyBase propertyBase)
   at lambda_method18(Closure, InternalEntityEntry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.OriginalValues..ctor(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntrySubscriber.SnapshotAndSubscribe(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
lbackState)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
nknownKey)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)

Include version information

Microsoft.Data.Sqlite version: 7.0.0
Target framework: net7.0
Operating system: Windows 10

@ajcvickers
Copy link
Member

Note for triage: only happens when an explicit column type is used.

@StringEpsilon As a workaround, don't specify HasColumnType. The column will still be created with the same type.

@StringEpsilon
Copy link
Author

That is something I can work with, thank you. I had no idea that having that mapping explicit made a difference in behavior like that.

Especially because this also has no issues:

	[Column("profession", TypeName = "integer")]
	public Profession Profession { get; set; }

Is that noted in the docs somewhere and I overlooked that?

@ajcvickers
Copy link
Member

It's a bug; it should not make a difference.

@ajcvickers ajcvickers self-assigned this Nov 18, 2022
@ajcvickers ajcvickers added this to the 8.0.0 milestone Nov 18, 2022
ajcvickers added a commit that referenced this issue Nov 21, 2022
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Nov 21, 2022
@ajcvickers ajcvickers modified the milestones: 8.0.0, 8.0.0-preview1 Jan 29, 2023
@ajcvickers ajcvickers modified the milestones: 8.0.0-preview1, 8.0.0 Nov 14, 2023
@ajcvickers ajcvickers removed their assignment Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-sqlite 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
2 participants