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

EF Core 8 & 9 throw when "external" sets of primitive types are included in select #35568

Open
ravindUwU opened this issue Feb 1, 2025 · 2 comments

Comments

@ravindUwU
Copy link

ravindUwU commented Feb 1, 2025

Bug description

I also noticed that when a variable that is a HashSet<int> (for example) is included in the select expression, EF Core 8 & 9 throw errors that seem to be JSON-serialization related. Like in #35567, this issue doesn't seem to occur when a non-primitive variable is included in the select expression.

Please refer to the reproduction and the stack trace included below 🙂.

  • EF core 7.0.20 runs the queries with no errors.

  • EF core 8.0.12 & 9.0.1 throw as described.

Your code

public class Program
{
	public static async Task Main()
	{
		var db = new Db();
		var listOfInt = new List<int> { 1 };
		var setOfDouble = new HashSet<double> { 1d };
		var anObject = new { };

		Write($"EF Core {typeof(DbContext).Assembly.GetName().Version}");

		Write("\n1. SELECT e.Id, listOfInt");
		await db.Entities.Select((e) => new { e.Id, listOfInt }).ToListAsync();

		Write("\n2. SELECT e.Id, setOfInt");
		try { await db.Entities.Select((e) => new { e.Id, setOfDouble }).ToListAsync(); }
		catch (Exception e) { Console.WriteLine(e); }

		Write("\n3. SELECT e.Id, listOfInt, setOfInt");
		try { await db.Entities.Select((e) => new { e.Id, listOfInt, setOfDouble }).ToListAsync(); }
		catch (Exception e) { Console.WriteLine(e); }

		Write("\n4. SELECT e.Id, listOfInt, setOfInt, anObject");
		try { await db.Entities.Select((e) => new { e.Id, listOfInt, setOfDouble, anObject }).ToListAsync(); }
		catch (Exception e) { Console.WriteLine(e); }
	}

	public class Db : DbContext
	{
		public DbSet<Entity> Entities { get; init; } = null!;

		protected override void OnConfiguring(DbContextOptionsBuilder options)
		{
			options.UseSqlServer("...");
			options.LogTo(Console.WriteLine, LogLevel.Information);
		}
	}

	[Table("entity")]
	[PrimaryKey(nameof(Id))]
	public class Entity
	{
		[Column("id")]
		public required int Id { get; set; }
	}

	public static void Write(string s)
	{
		var c = Console.ForegroundColor;
		Console.ForegroundColor = ConsoleColor.Cyan;
		Console.WriteLine(s);
		Console.ForegroundColor = c;
	}
}

Stack traces

With setOfDouble as a HashSet<double>, the stack trace is:

An exception occurred while iterating over the results of a query for context type 'TestEfCore.Program+Db'.
System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.HashSet`1[System.Double]' to type 'System.Collections.Generic.IList`1[System.Double]'.
   at Microsoft.EntityFrameworkCore.Storage.Json.JsonCollectionOfStructsReaderWriter`2.FromJsonTyped(Utf8JsonReaderManager& manager, Object existingObject)
   at Microsoft.EntityFrameworkCore.Storage.Json.JsonValueReaderWriter`1.FromJson(Utf8JsonReaderManager& manager, Object existingObject)
   at Microsoft.EntityFrameworkCore.Storage.Json.JsonValueReaderWriter.FromJsonString(String json, Object existingObject)
   at lambda_method11(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at TestEfCore.Program.Main() in ~\Projects\TestWeb\TestEfCore\Program.cs:line 27

As an IReadOnlySet<double> instead:

An exception occurred while iterating over the results of a query for context type 'TestEfCore.Program+Db'.
System.MissingMethodException: Cannot dynamically create an instance of type 'System.Collections.Generic.IReadOnlySet`1[System.Double]'. Reason: Cannot create an instance of an interface.
   at System.RuntimeType.ActivatorCache..ctor(RuntimeType rt)
   at System.RuntimeType.ActivatorCache.Create(RuntimeType type)
   at System.RuntimeType.IGenericCacheEntry`1.CreateAndCache(RuntimeType type)
   at System.RuntimeType.CreateInstanceOfT()
   at System.Activator.CreateInstance[T]()
   at Microsoft.EntityFrameworkCore.Storage.Json.JsonCollectionOfStructsReaderWriter`2.FromJsonTyped(Utf8JsonReaderManager& manager, Object existingObject)
   at Microsoft.EntityFrameworkCore.Storage.Json.JsonValueReaderWriter`1.FromJson(Utf8JsonReaderManager& manager, Object existingObject)
   at Microsoft.EntityFrameworkCore.Storage.Json.JsonValueReaderWriter.FromJsonString(String json, Object existingObject)
   at lambda_method11(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at TestEfCore.Program.Main() in ~\Projects\TestWeb\TestEfCore\Program.cs:line 27

Verbose output


EF Core version

9.0.1

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.NET 9.0.102

Operating system

Windows 11

IDE

Microsoft Visual Studio Professional 2022 x64 17.12.4

@ravindUwU
Copy link
Author

Noticed this with the SQLite provider as well, so I made a repro here: https://github.com/ravindUwU/dotnet-efcore-35568 😊

@maumar
Copy link
Contributor

maumar commented Feb 3, 2025

Problem here is that primitive collection variables are now processed as JSON parameters (https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew#parameterized-primitive-collections) However, our infrastructure requires JSON collections to be either arrays or implementing IList<T> (as we require them to be ordered) - in JsonCollectionOfStructsReaderWriter.FromJsonTyped we do hard cast to IList<TElement> which fails for Hashset<TElement>.

If Hashset<T> is used as a primitive collection on an entity, we throw a good exception:

 The type 'HashSet<double>' cannot be used as a primitive collection because it is not an array and does not implement 'IList<double?>'. Collections of primitive types must be arrays or ordered lists.

We should client eval primitive collections that don't implement IList

@maumar maumar added the type-bug label Feb 3, 2025
@maumar maumar added this to the Backlog milestone Feb 3, 2025
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

3 participants