diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index fd7a93208b0..8c9cd8e6df6 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -993,6 +993,12 @@ public static string EmptyComplexType(object? complexType) GetString("EmptyComplexType", nameof(complexType)), complexType); + /// + /// The empty string is not valid JSON. + /// + public static string EmptyJsonString + => GetString("EmptyJsonString"); + /// /// Cannot translate '{comparisonOperator}' on a subquery expression of entity type '{entityType}' because it has a composite primary key. See https://go.microsoft.com/fwlink/?linkid=2141942 for information on how to rewrite your query. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 45b45251ed8..e3f7f5d18d2 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -486,6 +486,9 @@ Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model. + + The empty string is not valid JSON. + Cannot translate '{comparisonOperator}' on a subquery expression of entity type '{entityType}' because it has a composite primary key. See https://go.microsoft.com/fwlink/?linkid=2141942 for information on how to rewrite your query. diff --git a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs index 92e2c2f3021..2a085ebbfed 100644 --- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs @@ -14,6 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Storage.Json; /// public abstract class JsonValueReaderWriter { + private static readonly bool UseOldBehavior32896 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32896", out var enabled32896) && enabled32896; + /// /// Ensures the external types extend from the generic /// @@ -63,6 +66,12 @@ internal JsonValueReaderWriter() /// The read value. public object FromJsonString(string json, object? existingObject = null) { + if (!UseOldBehavior32896 + && string.IsNullOrWhiteSpace(json)) + { + throw new InvalidOperationException(CoreStrings.EmptyJsonString); + } + var readerManager = new Utf8JsonReaderManager(new JsonReaderData(Encoding.UTF8.GetBytes(json)), null); return FromJson(ref readerManager, existingObject); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 98e6272c451..12ba1fc3033 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -1317,6 +1317,48 @@ FROM json_each(@__strings_0) AS "s" """); } + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] // Issue #32896 + public async Task Empty_string_used_for_primitive_collection_throws(bool async) + { + await using var connection = new SqliteConnection("DataSource=:memory:"); + await connection.OpenAsync(); + + await using var context = new SimpleContext(connection); + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + + await context.Database.ExecuteSqlRawAsync("INSERT INTO SimpleEntities (List) VALUES ('');"); + + var set = context.SimpleEntities; + + var message = await Assert.ThrowsAsync( + async () => + { + if (async) + { + await set.FirstAsync(); + } + else + { + set.First(); + } + }); + + Assert.Equal(CoreStrings.EmptyJsonString, message.Message); + } + + public class SimpleContext(SqliteConnection connection) : DbContext + { + public DbSet SimpleEntities => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite(connection); + } + + public record SimpleEntity(int Id, IEnumerable List); + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType());