From 7c9e2692fc5d600cdfef5d5fd06b5b242a24bec0 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Thu, 25 Jan 2024 20:05:33 +0000 Subject: [PATCH] Support empty strings in primitive collection columns Fixes #32896 --- src/EFCore/Properties/CoreStrings.Designer.cs | 6 ++ src/EFCore/Properties/CoreStrings.resx | 57 ++++++++++--------- .../Storage/Json/JsonValueReaderWriter.cs | 5 ++ .../PrimitiveCollectionsQuerySqliteTest.cs | 42 ++++++++++++++ 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 68556ca9280..1eb205020bb 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -999,6 +999,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 c2a088acf13..9d7fe91b5f7 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -489,6 +489,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 6d08502f54b..0536aa6ae43 100644 --- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs @@ -63,6 +63,11 @@ internal JsonValueReaderWriter() /// The read value. public object FromJsonString(string json, object? existingObject = null) { + if (string.IsNullOrWhiteSpace(json)) + { + throw new InvalidOperationException(CoreStrings.EmptyJsonString); + } + var readerManager = new Utf8JsonReaderManager(new JsonReaderData(Encoding.UTF8.GetBytes(json)), null); readerManager.MoveNext(); return FromJson(ref readerManager, existingObject); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 372f1e4ab3a..415eb4cd030 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -1385,6 +1385,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());