Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ private abstract class CosmosProjectionBindingRemovingExpressionVisitorBase(
bool trackQueryResults)
: ExpressionVisitor
{
private static readonly bool UseOldBehavior21006 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue21006", out var enabled21006) && enabled21006;

private static readonly MethodInfo GetItemMethodInfo
= typeof(JObject).GetRuntimeProperties()
.Single(pi => pi.Name == "Item" && pi.GetIndexParameters()[0].ParameterType == typeof(string))
Expand Down Expand Up @@ -691,7 +694,11 @@ private Expression CreateGetValueExpression(
&& !property.IsShadowProperty())
{
var readExpression = CreateGetValueExpression(
jTokenExpression, storeName, type.MakeNullable(), property.GetTypeMapping());
jTokenExpression,
storeName,
type.MakeNullable(),
property.GetTypeMapping(),
isNonNullableScalar: false);

var nonNullReadExpression = readExpression;
if (nonNullReadExpression.Type != type)
Expand All @@ -712,15 +719,23 @@ private Expression CreateGetValueExpression(
}

return Convert(
CreateGetValueExpression(jTokenExpression, storeName, type.MakeNullable(), property.GetTypeMapping()),
CreateGetValueExpression(
jTokenExpression,
storeName,
type.MakeNullable(),
property.GetTypeMapping(),
// special case keys - we check them for null to see if the entity needs to be materialized, so we want to keep the null, rather than non-nullable default
// returning defaults is supposed to help with evolving the schema - so this doesn't concern keys anyway (they shouldn't evolve)
isNonNullableScalar: !property.IsNullable && !property.IsKey()),
type);
}

private Expression CreateGetValueExpression(
Expression jTokenExpression,
string storeName,
Type type,
CoreTypeMapping typeMapping = null)
CoreTypeMapping typeMapping = null,
bool isNonNullableScalar = false)
{
Check.DebugAssert(type.IsNullableType(), "Must read nullable type from JObject.");

Expand Down Expand Up @@ -763,6 +778,7 @@ var body
Constant(CosmosClientWrapper.Serializer)),
converter.ConvertFromProviderExpression.Body);

var originalBodyType = body.Type;
if (body.Type != type)
{
body = Convert(body, type);
Expand All @@ -783,7 +799,11 @@ var body
}
else
{
replaceExpression = Default(type);
replaceExpression = isNonNullableScalar && !UseOldBehavior21006
? Expression.Convert(
Default(originalBodyType),
type)
: Default(type);
}

body = Condition(
Expand All @@ -799,7 +819,11 @@ var body
}
else
{
valueExpression = ConvertJTokenToType(jTokenExpression, typeMapping?.ClrType.MakeNullable() ?? type);
valueExpression = ConvertJTokenToType(
jTokenExpression,
(isNonNullableScalar && !UseOldBehavior21006
? typeMapping?.ClrType
: typeMapping?.ClrType.MakeNullable()) ?? type);

if (valueExpression.Type != type)
{
Expand Down
25 changes: 20 additions & 5 deletions src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2549,10 +2549,16 @@ private IReadOnlyList<MigrationOperation> RewriteOperations(
var newRawSchema = renameTableOperation.NewSchema;
var newSchema = newRawSchema ?? model?.GetDefaultSchema();

var temporalTableInformation = BuildTemporalInformationFromMigrationOperation(schema, renameTableOperation);
if (!temporalTableInformationMap.ContainsKey((tableName, rawSchema)))
{
var temporalTableInformation = BuildTemporalInformationFromMigrationOperation(schema, renameTableOperation);
temporalTableInformationMap[(tableName, rawSchema)] = temporalTableInformation;
}

// we still need to check here - table with the new name could have existed before and have been deleted
// we want to preserve the original temporal info of that deleted table
if (!temporalTableInformationMap.ContainsKey((newTableName, newRawSchema)))
{
temporalTableInformationMap[(newTableName, newRawSchema)] = temporalTableInformation;
}

Expand Down Expand Up @@ -2647,10 +2653,19 @@ private IReadOnlyList<MigrationOperation> RewriteOperations(

var schema = rawSchema ?? model?.GetDefaultSchema();

// we are guaranteed to find entry here - we looped through all the operations earlier,
// info missing from operations we got from the model
// and in case of no/incomplete model we created dummy (non-temporal) entries
var temporalInformation = temporalTableInformationMap[(tableName, rawSchema)];
TemporalOperationInformation temporalInformation;
if (operation is CreateTableOperation)
{
// for create table we always generate new temporal information from the operation itself
// just in case there was a table with that name before that got deleted/renamed
// also, temporal state (disabled versioning etc.) should always reset when creating a table
temporalInformation = BuildTemporalInformationFromMigrationOperation(schema, operation);
temporalTableInformationMap[(tableName, rawSchema)] = temporalInformation;
}
else
{
temporalInformation = temporalTableInformationMap[(tableName, rawSchema)];
}

switch (operation)
{
Expand Down
43 changes: 43 additions & 0 deletions test/EFCore.Cosmos.FunctionalTests/Query/AdHocCosmosTestHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net;
using Microsoft.Azure.Cosmos;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.EntityFrameworkCore.Query;

public class AdHocCosmosTestHelpers
{
public static async Task CreateCustomEntityHelperAsync(
Container container,
string json,
CancellationToken cancellationToken)
{
var document = JObject.Parse(json);

var stream = new MemoryStream();
await using var __ = stream.ConfigureAwait(false);
var writer = new StreamWriter(stream, new UTF8Encoding(), bufferSize: 1024, leaveOpen: false);
await using var ___ = writer.ConfigureAwait(false);
using var jsonWriter = new JsonTextWriter(writer);

CosmosClientWrapper.Serializer.Serialize(jsonWriter, document);
await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false);

var response = await container.CreateItemStreamAsync(
stream,
PartitionKey.None,
requestOptions: null,
cancellationToken)
.ConfigureAwait(false);


if (response.StatusCode != HttpStatusCode.Created)
{
throw new InvalidOperationException($"Failed to create entity (status code: {response.StatusCode}) for json: {json}");
}
}
}
Loading
Loading