Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/EFCore.Relational/EFCore.Relational.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -15675,6 +15675,9 @@
{
"Member": "static string JsonElementMappingNotFound(object? structuralType, object? name, object? columnName);"
},
{
"Member": "static string JsonEntityCollectionExpectedArray(object? entityType, object? navigation, object? tokenType);"
},
{
"Member": "static string JsonEntityCollectionExpectedObject(object? entityType, object? navigation);"
},
Expand Down Expand Up @@ -15750,6 +15753,9 @@
{
"Member": "static string JsonRequiredEntityWithNullJson(object? entity);"
},
{
"Member": "static string JsonStructuralTypeExpectedObject(object? structuralType, object? tokenType);"
},
{
"Member": "static string JsonValueReadWriterMissingOnTypeMapping(object? typeMapping, object? property, object? entityType);"
},
Expand Down
16 changes: 16 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,9 @@
<data name="JsonEmptyString" xml:space="preserve">
<value>The database returned the empty string when a JSON object was expected.</value>
</data>
<data name="JsonEntityCollectionExpectedArray" xml:space="preserve">
<value>The JSON for '{entityType}.{navigation}' contains a '{tokenType}' token when a JSON array was expected.</value>
</data>
<data name="JsonEntityCollectionExpectedObject" xml:space="preserve">
<value>The JSON array for '{entityType}.{navigation}' contains a non-object element when a JSON object was expected.</value>
</data>
Expand Down Expand Up @@ -775,6 +778,9 @@
<data name="JsonRequiredEntityWithNullJson" xml:space="preserve">
<value>Entity {entity} is required but the JSON element containing it is 'null'.</value>
</data>
<data name="JsonStructuralTypeExpectedObject" xml:space="preserve">
<value>The JSON for '{structuralType}' contains a '{tokenType}' token when a JSON object was expected.</value>
</data>
<data name="JsonValueReadWriterMissingOnTypeMapping" xml:space="preserve">
<value>Type mapping type '{typeMapping}', which is being used on property '{property}' on entity type '{entityType}' in a JSON document, has not defined a JsonValueReaderWriter.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ static async Task<RelationalDataReader> InitializeReaderAsync(

case not JsonTokenType.StartObject:
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
RelationalStrings.JsonStructuralTypeExpectedObject(typeof(TStructural).Name, tokenType));
}

manager.CaptureState();
Expand Down Expand Up @@ -1101,7 +1101,7 @@ static async Task<RelationalDataReader> InitializeReaderAsync(

case not JsonTokenType.StartObject:
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
RelationalStrings.JsonStructuralTypeExpectedObject(typeof(TStructural).Name, tokenType));
}

manager.CaptureState();
Expand Down Expand Up @@ -1144,7 +1144,11 @@ static async Task<RelationalDataReader> InitializeReaderAsync(
return default;

case not JsonTokenType.StartArray:
throw new InvalidOperationException(CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
throw new InvalidOperationException(
RelationalStrings.JsonEntityCollectionExpectedArray(
structuralProperty.DeclaringType.DisplayName(),
structuralProperty.Name,
tokenType));
}

var collectionAccessor = structuralProperty.GetCollectionAccessor();
Expand Down Expand Up @@ -1175,7 +1179,7 @@ static async Task<RelationalDataReader> InitializeReaderAsync(
if (manager.CurrentReader.TokenType != JsonTokenType.EndObject)
{
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonReaderInvalidTokenType(manager.CurrentReader.TokenType.ToString()));
}

tokenType = manager.MoveNext();
Expand Down Expand Up @@ -1232,7 +1236,7 @@ public static void IncludeJsonEntityReference<TStructural, TRelatedStructural>(

case not JsonTokenType.StartObject:
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
RelationalStrings.JsonStructuralTypeExpectedObject(typeof(TRelatedStructural).Name, tokenType));
}

var included = innerShaper(queryContext, keyPropertyValues, jsonReaderData);
Expand All @@ -1255,6 +1259,7 @@ public static void IncludeJsonEntityCollection<TIncludingEntity, TIncludedCollec
object[]? keyPropertyValues,
JsonReaderData? jsonReaderData,
TIncludingEntity entity,
IPropertyBase structuralProperty,
Func<QueryContext, object[]?, JsonReaderData, TIncludedCollectionElement> innerShaper,
Action<TIncludingEntity> getOrCreateCollectionObject,
Action<TIncludingEntity, TIncludedCollectionElement> fixup,
Expand All @@ -1277,7 +1282,10 @@ public static void IncludeJsonEntityCollection<TIncludingEntity, TIncludedCollec

case not JsonTokenType.StartArray:
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
RelationalStrings.JsonEntityCollectionExpectedArray(
structuralProperty.DeclaringType.DisplayName(),
structuralProperty.Name,
tokenType));
}

getOrCreateCollectionObject(entity);
Expand Down Expand Up @@ -1311,15 +1319,24 @@ public static void IncludeJsonEntityCollection<TIncludingEntity, TIncludedCollec
if (manager.CurrentReader.TokenType != JsonTokenType.EndObject)
{
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonReaderInvalidTokenType(manager.CurrentReader.TokenType.ToString()));
}

tokenType = manager.MoveNext();
}
else if (tokenType == JsonTokenType.Null)
{
throw new InvalidOperationException(
RelationalStrings.JsonEntityCollectionNullElement(
structuralProperty.DeclaringType.DisplayName(),
structuralProperty.Name));
}
else
{
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
RelationalStrings.JsonEntityCollectionExpectedObject(
structuralProperty.DeclaringType.DisplayName(),
structuralProperty.Name));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,11 @@ private Expression CreateJsonShapers(
keyValuesParameter,
jsonReaderDataParameter,
includingEntityExpression,
_parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant(
structuralProperty,
LiftableConstantExpressionHelpers.BuildStructuralPropertyAccessLambda(structuralProperty),
structuralProperty.Name + "StructuralProperty",
typeof(IPropertyBase)),
shaperLambda,
GetOrCreateCollectionObjectLambda(declaringClrType, structuralProperty),
fixup,
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/EFCore.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -4144,6 +4144,12 @@
{
"Member": "static string IQueryableNotAsync(object? genericParameter);"
},
{
"Member": "static string JsonCollectionExpectedArray(object? elementType, object? tokenType);"
},
{
"Member": "static string JsonCollectionInvalidElement(object? elementType, object? tokenType);"
},
{
"Member": "static string JsonReaderInvalidTokenType(object? tokenType);"
},
Expand Down
16 changes: 16 additions & 0 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,12 @@
<data name="IQueryableProviderNotAsync" xml:space="preserve">
<value>The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations.</value>
</data>
<data name="JsonCollectionExpectedArray" xml:space="preserve">
<value>The JSON for a collection of '{elementType}' contains a '{tokenType}' token when a JSON array was expected.</value>
</data>
<data name="JsonCollectionInvalidElement" xml:space="preserve">
<value>The JSON array for a collection of '{elementType}' contains a '{tokenType}' token, which is not a valid element.</value>
</data>
<data name="JsonReaderInvalidTokenType" xml:space="preserve">
<value>Invalid token type: '{tokenType}'.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public JsonCollectionOfNullableStructsReaderWriter(JsonValueReaderWriter<TElemen
if (tokenType != JsonTokenType.StartArray)
{
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonCollectionExpectedArray(typeof(TElement).ShortDisplayName(), tokenType));
}

while (tokenType != JsonTokenType.EndArray)
Expand Down Expand Up @@ -91,7 +91,7 @@ public JsonCollectionOfNullableStructsReaderWriter(JsonValueReaderWriter<TElemen
case JsonTokenType.PropertyName:
default:
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonCollectionInvalidElement(typeof(TElement).ShortDisplayName(), tokenType));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public override object FromJsonTyped(ref Utf8JsonReaderManager manager, object?
if (tokenType != JsonTokenType.StartArray)
{
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonCollectionExpectedArray(typeof(TElement).ShortDisplayName(), tokenType));
}

while (tokenType != JsonTokenType.EndArray)
Expand Down Expand Up @@ -83,7 +83,7 @@ public override object FromJsonTyped(ref Utf8JsonReaderManager manager, object?
case JsonTokenType.PropertyName:
default:
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonCollectionInvalidElement(typeof(TElement).ShortDisplayName(), tokenType));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public override IEnumerable<TElement> FromJsonTyped(ref Utf8JsonReaderManager ma
if (tokenType != JsonTokenType.StartArray)
{
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonCollectionExpectedArray(typeof(TElement).ShortDisplayName(), tokenType));
}

while (tokenType != JsonTokenType.EndArray)
Expand Down Expand Up @@ -84,7 +84,7 @@ public override IEnumerable<TElement> FromJsonTyped(ref Utf8JsonReaderManager ma
case JsonTokenType.PropertyName:
default:
throw new InvalidOperationException(
CoreStrings.JsonReaderInvalidTokenType(tokenType.ToString()));
CoreStrings.JsonCollectionInvalidElement(typeof(TElement).ShortDisplayName(), tokenType));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,10 @@ public override async Task Try_project_collection_but_JSON_is_entity()
var message = (await Assert.ThrowsAsync<InvalidOperationException>(() => base.Try_project_collection_but_JSON_is_entity())).Message;

Assert.Equal(
CoreStrings.JsonReaderInvalidTokenType(nameof(JsonTokenType.StartObject)),
RelationalStrings.JsonEntityCollectionExpectedArray(
typeof(Context34960.JunkEntity).ShortDisplayName(),
nameof(Context34960.JunkEntity.Collection),
nameof(JsonTokenType.StartObject)),
message);
}

Expand All @@ -849,7 +852,9 @@ public override async Task Try_project_reference_but_JSON_is_collection()
.Message;

Assert.Equal(
CoreStrings.JsonReaderInvalidTokenType(nameof(JsonTokenType.StartArray)),
RelationalStrings.JsonStructuralTypeExpectedObject(
nameof(Context34960.JsonEntity),
nameof(JsonTokenType.StartArray)),
message);
}

Expand Down Expand Up @@ -1778,5 +1783,95 @@ public class SubItem

#endregion

#region 39486

[Fact]
public virtual Task Include_json_collection_with_null_element_throws()
=> AssertQueryThrows39486(
1,
RelationalStrings.JsonEntityCollectionNullElement(
typeof(Context39486.Item).ShortDisplayName(),
nameof(Context39486.Item.SubItems)));

[Fact]
public virtual Task Include_json_collection_with_non_object_element_throws()
=> AssertQueryThrows39486(
2,
RelationalStrings.JsonEntityCollectionExpectedObject(
typeof(Context39486.Item).ShortDisplayName(),
nameof(Context39486.Item.SubItems)));

[Fact]
public virtual Task Include_json_collection_but_JSON_is_entity_throws()
=> AssertQueryThrows39486(
3,
RelationalStrings.JsonEntityCollectionExpectedArray(
typeof(Context39486.Item).ShortDisplayName(),
nameof(Context39486.Item.SubItems),
nameof(JsonTokenType.StartObject)));

[Fact]
public virtual Task Include_json_reference_but_JSON_is_collection_throws()
=> AssertQueryThrows39486(
4,
RelationalStrings.JsonStructuralTypeExpectedObject(
nameof(Context39486.ItemDetails),
nameof(JsonTokenType.StartArray)));

private async Task AssertQueryThrows39486(int id, string expectedMessage)
{
var contextFactory = await InitializeNonSharedTest<Context39486>(
onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings),
onModelCreating: m => m.Entity<Context39486.Item>(b =>
{
b.ToTable("Items");
b.Property(x => x.Id).ValueGeneratedNever();
b.OwnsOne(x => x.Details).ToJson().HasColumnType(JsonColumnType);
b.OwnsMany(x => x.SubItems).ToJson().HasColumnType(JsonColumnType);
}),
seed: async context =>
{
var sqlGenerationHelper = context.GetService<ISqlGenerationHelper>();

await context.Database.ExecuteSqlRawAsync(
$$$"""INSERT INTO {{{Q("Items")}}} ({{{Q("Id")}}}, {{{Q("SubItems")}}}) VALUES (1, '[null]'), (2, '[42]'), (3, '{{"Name":"x"}}')""");

await context.Database.ExecuteSqlRawAsync(
$$$"""INSERT INTO {{{Q("Items")}}} ({{{Q("Id")}}}, {{{Q("Details")}}}) VALUES (4, '[{{"Description":"x"}}]')""");

string Q(string name)
=> sqlGenerationHelper.DelimitIdentifier(name);
});

await using var context = contextFactory.CreateDbContext();

var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => context.Set<Context39486.Item>().Where(x => x.Id == id).ToListAsync());

Assert.Equal(expectedMessage, exception.Message);
}

protected class Context39486(DbContextOptions options) : DbContext(options)
{
public class Item
{
public int Id { get; set; }
public ItemDetails? Details { get; set; }
public List<SubItem> SubItems { get; set; } = [];
}

public class ItemDetails
{
public string? Description { get; set; }
}

public class SubItem
{
public string? Name { get; set; }
}
}

#endregion

#endregion
}
Loading
Loading