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

[3.1.1] Set ordinal key values for embedded owned collection entities #19100

Merged
merged 2 commits into from
Dec 10, 2019
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
35 changes: 35 additions & 0 deletions src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class CosmosPropertyExtensions
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static bool IsOrdinalKeyProperty(this IProperty property)
{
Debug.Assert(property.DeclaringEntityType.IsOwned());
Debug.Assert(property.GetPropertyName().Length == 0);

return property.IsPrimaryKey()
&& !property.IsForeignKey()
&& property.ClrType == typeof(int)
&& property.ValueGenerated == ValueGenerated.OnAdd
&& property.DeclaringEntityType.FindPrimaryKey().Properties.Count > 1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -604,9 +604,7 @@ private Expression CreateGetValueExpression(
{
var ownership = entityType.FindOwnership();
if (!ownership.IsUnique
&& property.IsPrimaryKey()
&& !property.IsForeignKey()
&& property.ClrType == typeof(int))
&& property.IsOrdinalKeyProperty())
{
Expression readExpression = _ordinalParameterBindings[jObjectExpression];
if (readExpression.Type != clrType)
Expand Down
100 changes: 95 additions & 5 deletions src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Linq;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
Expand Down Expand Up @@ -64,6 +66,15 @@ public virtual string GetId(IUpdateEntry entry)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual JObject CreateDocument(IUpdateEntry entry)
=> CreateDocument(entry, null);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual JObject CreateDocument(IUpdateEntry entry, int? ordinal)
{
var document = new JObject();
foreach (var property in entry.EntityType.GetProperties())
Expand All @@ -75,7 +86,15 @@ public virtual JObject CreateDocument(IUpdateEntry entry)
}
else if (entry.HasTemporaryValue(property))
{
((InternalEntityEntry)entry)[property] = entry.GetCurrentValue(property);
if (AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue18948", out var isEnabled) && isEnabled)
{
((InternalEntityEntry)entry)[property] = entry.GetCurrentValue(property);
}
else if (ordinal != null
&& property.IsOrdinalKeyProperty())
{
entry.SetStoreGeneratedValue(property, ordinal.Value);
}
}
}

Expand All @@ -102,11 +121,13 @@ public virtual JObject CreateDocument(IUpdateEntry entry)
}
else
{
var embeddedOrdinal = 0;
var array = new JArray();
foreach (var dependent in (IEnumerable)embeddedValue)
{
var dependentEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(dependent, fk.DeclaringEntityType);
array.Add(_database.GetDocumentSource(dependentEntry.EntityType).CreateDocument(dependentEntry));
array.Add(_database.GetDocumentSource(dependentEntry.EntityType).CreateDocument(dependentEntry, embeddedOrdinal));
embeddedOrdinal++;
}

document[embeddedPropertyName] = array;
Expand All @@ -123,8 +144,18 @@ public virtual JObject CreateDocument(IUpdateEntry entry)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)
=> UpdateDocument(document, entry, null);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry, int? ordinal)
{
var anyPropertyUpdated = false;
var stateManager = ((InternalEntityEntry)entry).StateManager;
foreach (var property in entry.EntityType.GetProperties())
{
if (entry.EntityState == EntityState.Added
Expand All @@ -135,11 +166,21 @@ public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)
{
document[storeName] = ConvertPropertyValue(property, entry.GetCurrentValue(property));
anyPropertyUpdated = true;
continue;
}
else if (entry.HasTemporaryValue(property))
}

if (entry.HasTemporaryValue(property))
{
if (AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue18948", out var isEnabled) && isEnabled)
{
((InternalEntityEntry)entry)[property] = entry.GetCurrentValue(property);
}
else if (ordinal != null
&& property.IsOrdinalKeyProperty())
{
entry.SetStoreGeneratedValue(property, ordinal.Value);
}
}
}

Expand Down Expand Up @@ -185,6 +226,50 @@ public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)
}
else
{
var embeddedOrdinal = 0;
if (!AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue18948", out var isEnabled) || !isEnabled)
{
var ordinalKeyProperty = GetOrdinalKeyProperty(fk.DeclaringEntityType);
if (ordinalKeyProperty != null)
{
var shouldSetTemporaryKeys = false;
foreach (var dependent in (IEnumerable)embeddedValue)
{
var embeddedEntry = stateManager.TryGetEntry(dependent, fk.DeclaringEntityType);
if (embeddedEntry == null)
{
continue;
}

if ((int)embeddedEntry.GetCurrentValue(ordinalKeyProperty) != embeddedOrdinal)
{
shouldSetTemporaryKeys = true;
break;
}

embeddedOrdinal++;
}

if (shouldSetTemporaryKeys)
{
var temporaryOrdinal = -1;
foreach (var dependent in (IEnumerable)embeddedValue)
{
var embeddedEntry = stateManager.TryGetEntry(dependent, fk.DeclaringEntityType);
if (embeddedEntry == null)
{
continue;
}

embeddedEntry.SetTemporaryValue(ordinalKeyProperty, temporaryOrdinal, setModified: false);

temporaryOrdinal--;
}
}
}
}

embeddedOrdinal = 0;
var array = new JArray();
foreach (var dependent in (IEnumerable)embeddedValue)
{
Expand All @@ -196,10 +281,11 @@ public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)

var embeddedDocument = embeddedDocumentSource.GetCurrentDocument(embeddedEntry);
embeddedDocument = embeddedDocument != null
? embeddedDocumentSource.UpdateDocument(embeddedDocument, embeddedEntry) ?? embeddedDocument
: embeddedDocumentSource.CreateDocument(embeddedEntry);
? embeddedDocumentSource.UpdateDocument(embeddedDocument, embeddedEntry, embeddedOrdinal) ?? embeddedDocument
: embeddedDocumentSource.CreateDocument(embeddedEntry, embeddedOrdinal);

array.Add(embeddedDocument);
embeddedOrdinal++;
}

document[embeddedPropertyName] = array;
Expand All @@ -210,6 +296,10 @@ public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)
return anyPropertyUpdated ? document : null;
}

private IProperty GetOrdinalKeyProperty(IEntityType entityType)
=> entityType.FindPrimaryKey().Properties.FirstOrDefault(p =>
p.GetPropertyName().Length == 0 && p.IsOrdinalKeyProperty());

public virtual JObject GetCurrentDocument(IUpdateEntry entry)
=> _jObjectProperty != null
? (JObject)(entry.SharedIdentityEntry ?? entry).GetCurrentValue(_jObjectProperty)
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/ChangeTracking/Internal/IStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ public interface IStateManager : IResettableService
/// </summary>
CascadeTiming CascadeDeleteTiming { get; set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
bool SavingChanges { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
11 changes: 9 additions & 2 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,9 @@ public virtual void SetPropertyModified(
if (changeState
&& !isConceptualNull
&& isModified
&& property.IsKey())
&& !StateManager.SavingChanges
&& property.IsKey()
&& property.GetAfterSaveBehavior() == PropertySaveBehavior.Throw)
{
throw new InvalidOperationException(CoreStrings.KeyReadOnly(property.Name, EntityType.DisplayName()));
}
Expand Down Expand Up @@ -608,7 +610,12 @@ public virtual void SetStoreGeneratedValue(IProperty property, object value)
}

SetProperty(
property, value, isMaterialization: false, setModified: true, isCascadeDelete: false, CurrentValueType.StoreGenerated);
property,
value,
isMaterialization: false,
setModified: true,
isCascadeDelete: false,
CurrentValueType.StoreGenerated);
}

/// <summary>
Expand Down
21 changes: 20 additions & 1 deletion src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ public StateManager([NotNull] StateManagerDependencies dependencies)
/// </summary>
public virtual CascadeTiming CascadeDeleteTiming { get; set; } = CascadeTiming.Immediate;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool SavingChanges { get; set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -651,6 +659,8 @@ public virtual void ResetState()

Tracked = null;
StateChanged = null;

SavingChanges = false;
}

/// <summary>
Expand Down Expand Up @@ -1062,7 +1072,6 @@ protected virtual async Task<int> SaveChangesAsync(
}
}


/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -1089,6 +1098,7 @@ private int SaveChanges(DbContext _, bool acceptAllChangesOnSuccess)

try
{
SavingChanges = true;
var result = SaveChanges(entriesToSave);

if (acceptAllChangesOnSuccess)
Expand All @@ -1107,6 +1117,10 @@ private int SaveChanges(DbContext _, bool acceptAllChangesOnSuccess)

throw;
}
finally
{
SavingChanges = false;
}
}

/// <summary>
Expand Down Expand Up @@ -1137,6 +1151,7 @@ private async Task<int> SaveChangesAsync(

try
{
SavingChanges = true;
var result = await SaveChangesAsync(entriesToSave, cancellationToken);

if (acceptAllChangesOnSuccess)
Expand All @@ -1155,6 +1170,10 @@ private async Task<int> SaveChangesAsync(

throw;
}
finally
{
SavingChanges = false;
}
}

/// <summary>
Expand Down
Loading