Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Don't persist keys for embedded entities
Browse files Browse the repository at this point in the history
Fixes #13578
Part of #12086
AndriySvyryd committed Jul 27, 2019
1 parent b9653e8 commit 8f50ca5
Showing 26 changed files with 394 additions and 377 deletions.
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ public static class CosmosDbContextOptionsExtensions
/// <param name="accountEndpoint"> The account end-point to connect to. </param>
/// <param name="accountKey"> The account key. </param>
/// <param name="databaseName"> The database name. </param>
/// <param name="cosmosOptionsAction">An optional action to allow additional Cosmos-specific configuration.</param>
/// <param name="cosmosOptionsAction"> An optional action to allow additional Cosmos-specific configuration. </param>
/// <returns> The options builder so that further configuration can be chained. </returns>
public static DbContextOptionsBuilder<TContext> UseCosmos<TContext>(
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
@@ -46,7 +46,7 @@ public static DbContextOptionsBuilder<TContext> UseCosmos<TContext>(
/// <param name="accountEndpoint"> The account end-point to connect to. </param>
/// <param name="accountKey"> The account key. </param>
/// <param name="databaseName"> The database name. </param>
/// <param name="cosmosOptionsAction">An optional action to allow additional Cosmos-specific configuration.</param>
/// <param name="cosmosOptionsAction"> An optional action to allow additional Cosmos-specific configuration. </param>
/// <returns> The options builder so that further configuration can be chained. </returns>
public static DbContextOptionsBuilder UseCosmos(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
23 changes: 22 additions & 1 deletion src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -20,7 +21,27 @@ public static class CosmosPropertyExtensions
/// <returns> The property name used when targeting Cosmos. </returns>
public static string GetCosmosPropertyName([NotNull] this IProperty property) =>
(string)property[CosmosAnnotationNames.PropertyName]
?? property.Name;
?? GetDefaultPropertyName(property);

private static string GetDefaultPropertyName(IProperty property)
{
var entityType = property.DeclaringEntityType;
var ownership = entityType.FindOwnership();

if (ownership != null
&& !entityType.IsDocumentRoot())
{
var pk = property.FindContainingPrimaryKey();
if (pk != null
&& pk.Properties.Count == ownership.Properties.Count + (ownership.IsUnique ? 0 : 1)
&& ownership.Properties.All(fkProperty => pk.Properties.Contains(fkProperty)))
{
return "";
}
}

return property.Name;
}

/// <summary>
/// Sets the property name used when targeting Cosmos.
Original file line number Diff line number Diff line change
@@ -9,12 +9,14 @@
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.EntityFrameworkCore.ValueGeneration;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
@@ -53,6 +55,7 @@ public static IServiceCollection AddEntityFrameworkCosmos([NotNull] this IServic
.TryAdd<IDbContextTransactionManager, CosmosTransactionManager>()
.TryAdd<IModelValidator, CosmosModelValidator>()
.TryAdd<IProviderConventionSetBuilder, CosmosConventionSetBuilder>()
.TryAdd<IValueGeneratorSelector, CosmosValueGeneratorSelector>()
.TryAdd<IDatabaseCreator, CosmosDatabaseCreator>()
.TryAdd<IQueryContextFactory, CosmosQueryContextFactory>()
.TryAdd<ITypeMappingSource, CosmosTypeMappingSource>()
19 changes: 13 additions & 6 deletions src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs
Original file line number Diff line number Diff line change
@@ -9,15 +9,16 @@
using Microsoft.EntityFrameworkCore.Utilities;
using Newtonsoft.Json.Linq;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// <para>
/// A convention that adds the 'id' property - a key required by Azure Cosmos.
/// </para>
/// This convention also add the '__jObject' containing the JSON object returned by the store.
/// <para>
/// </para>
/// This convention also adds the '__jObject' containing the JSON object returned by the store.
/// </para>
/// </summary>
public class StoreKeyConvention :
IEntityTypeAddedConvention,
@@ -52,10 +53,6 @@ private static void Process(IConventionEntityTypeBuilder entityTypeBuilder)
var idProperty = entityTypeBuilder.Property(typeof(string), IdPropertyName);
idProperty.HasValueGenerator((_, __) => new IdValueGenerator());
entityTypeBuilder.HasKey(new[] { idProperty.Metadata });

var jObjectProperty = entityTypeBuilder.Property(typeof(JObject), JObjectPropertyName);
jObjectProperty.ToJsonProperty("");
jObjectProperty.ValueGenerated(ValueGenerated.OnAddOrUpdate);
}
else
{
@@ -68,7 +65,17 @@ private static void Process(IConventionEntityTypeBuilder entityTypeBuilder)
entityType.Builder.HasNoKey(key);
}
}
}

if (entityType.BaseType == null
&& !entityType.IsKeyless)
{
var jObjectProperty = entityTypeBuilder.Property(typeof(JObject), JObjectPropertyName);
jObjectProperty.ToJsonProperty("");
jObjectProperty.ValueGenerated(ValueGenerated.OnAddOrUpdate);
}
else
{
var jObjectProperty = entityType.FindDeclaredProperty(JObjectPropertyName);
if (jObjectProperty != null)
{
Original file line number Diff line number Diff line change
@@ -200,7 +200,8 @@ protected override Expression VisitMember(MemberExpression memberExpression)
throw new InvalidOperationException();
}

var navigationProjection = innerEntityProjection.BindMember(memberExpression.Member, innerExpression.Type, out var propertyBase);
var navigationProjection = innerEntityProjection.BindMember(
memberExpression.Member, innerExpression.Type, clientEval: true, out var propertyBase);

if (!(propertyBase is INavigation navigation)
|| !navigation.IsEmbedded())

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -105,9 +105,9 @@ private bool TryBindMember(Expression source, MemberIdentity member, out Express
if (source is EntityProjectionExpression entityProjectionExpression)
{
expression = member.MemberInfo != null
? entityProjectionExpression.BindMember(member.MemberInfo, null, out _)
: entityProjectionExpression.BindMember(member.Name, null, out _);
return true;
? entityProjectionExpression.BindMember(member.MemberInfo, null, clientEval: false, out _)
: entityProjectionExpression.BindMember(member.Name, null, clientEval: false, out _);
return expression != null;
}

if (source is MemberExpression innerMemberExpression
60 changes: 44 additions & 16 deletions src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs
Original file line number Diff line number Diff line change
@@ -19,10 +19,10 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
/// </summary>
public class EntityProjectionExpression : Expression, IPrintable, IAccessExpression
{
private readonly IDictionary<IProperty, SqlExpression> _propertyExpressionsCache
= new Dictionary<IProperty, SqlExpression>();
private readonly IDictionary<INavigation, Expression> _navigationExpressionsCache
= new Dictionary<INavigation, Expression>();
private readonly IDictionary<IProperty, IAccessExpression> _propertyExpressionsCache
= new Dictionary<IProperty, IAccessExpression>();
private readonly IDictionary<INavigation, IAccessExpression> _navigationExpressionsCache
= new Dictionary<INavigation, IAccessExpression>();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -98,7 +98,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
/// 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 SqlExpression BindProperty(IProperty property)
public virtual Expression BindProperty(IProperty property, bool clientEval)
{
if (!EntityType.IsAssignableFrom(property.DeclaringEntityType)
&& !property.DeclaringEntityType.IsAssignableFrom(EntityType))
@@ -113,7 +113,14 @@ public virtual SqlExpression BindProperty(IProperty property)
_propertyExpressionsCache[property] = expression;
}

return expression;
if (!clientEval
&& expression.Name.Length == 0)
{
// Non-persisted property can't be translated
return null;
}

return (Expression)expression;
}

/// <summary>
@@ -122,7 +129,7 @@ public virtual SqlExpression BindProperty(IProperty property)
/// 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 Expression BindNavigation(INavigation navigation)
public virtual Expression BindNavigation(INavigation navigation, bool clientEval)
{
if (!EntityType.IsAssignableFrom(navigation.DeclaringEntityType)
&& !navigation.DeclaringEntityType.IsAssignableFrom(EntityType))
@@ -147,7 +154,14 @@ public virtual Expression BindNavigation(INavigation navigation)
_navigationExpressionsCache[navigation] = expression;
}

return expression;
if (!clientEval
&& expression.Name.Length == 0)
{
// Non-persisted navigation can't be translated
return null;
}

return (Expression)expression;
}

/// <summary>
@@ -156,7 +170,7 @@ public virtual Expression BindNavigation(INavigation navigation)
/// 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 Expression BindMember(string name, Type entityClrType, out IPropertyBase propertyBase)
public virtual Expression BindMember(string name, Type entityClrType, bool clientEval, out IPropertyBase propertyBase)
{
var entityType = EntityType;
if (entityClrType != null
@@ -169,12 +183,19 @@ public virtual Expression BindMember(string name, Type entityClrType, out IPrope
if (property != null)
{
propertyBase = property;
return BindProperty(property);
return BindProperty(property, clientEval);
}

var navigation = entityType.FindNavigation(name);
propertyBase = navigation;
return BindNavigation(navigation);
if (navigation != null)
{
propertyBase = navigation;
return BindNavigation(navigation, clientEval);
}

// Entity member not found
propertyBase = null;
return null;
}

/// <summary>
@@ -183,7 +204,7 @@ public virtual Expression BindMember(string name, Type entityClrType, out IPrope
/// 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 Expression BindMember(MemberInfo memberInfo, Type entityClrType, out IPropertyBase propertyBase)
public virtual Expression BindMember(MemberInfo memberInfo, Type entityClrType, bool clientEval, out IPropertyBase propertyBase)
{
var entityType = EntityType;
if (entityClrType != null
@@ -196,12 +217,19 @@ public virtual Expression BindMember(MemberInfo memberInfo, Type entityClrType,
if (property != null)
{
propertyBase = property;
return BindProperty(property);
return BindProperty(property, clientEval);
}

var navigation = entityType.FindNavigation(memberInfo);
propertyBase = navigation;
return BindNavigation(navigation);
if (navigation != null)
{
propertyBase = navigation;
return BindNavigation(navigation, clientEval);
}

// Entity member not found
propertyBase = null;
return null;
}

/// <summary>
33 changes: 0 additions & 33 deletions src/EFCore.Cosmos/Query/Internal/IShaper.cs

This file was deleted.

10 changes: 9 additions & 1 deletion src/EFCore.Cosmos/Query/Internal/ObjectAccessExpression.cs
Original file line number Diff line number Diff line change
@@ -28,13 +28,21 @@ public ObjectAccessExpression(INavigation navigation, Expression accessExpressio
if (Name == null)
{
throw new InvalidOperationException(
$"Navigation '{navigation.DeclaringEntityType.DisplayName()}.{navigation.Name}' doesn't point to a nested entity.");
$"Navigation '{navigation.DeclaringEntityType.DisplayName()}.{navigation.Name}' doesn't point to an embedded entity.");
}

Navigation = navigation;
AccessExpression = accessExpression;
}

/// <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 override ExpressionType NodeType => ExpressionType.Extension;

/// <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
8 changes: 4 additions & 4 deletions src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
@@ -514,19 +514,19 @@ private void AddDiscriminator(SelectExpression selectExpression, IEntityType ent
if (concreteEntityType.GetDiscriminatorProperty() != null)
{
var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember()))
.BindProperty(concreteEntityType.GetDiscriminatorProperty());
.BindProperty(concreteEntityType.GetDiscriminatorProperty(), clientEval: false);

selectExpression.ApplyPredicate(
Equal(discriminatorColumn, Constant(concreteEntityType.GetDiscriminatorValue())));
Equal((SqlExpression)discriminatorColumn, Constant(concreteEntityType.GetDiscriminatorValue())));
}
}
else
{
var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember()))
.BindProperty(concreteEntityTypes[0].GetDiscriminatorProperty());
.BindProperty(concreteEntityTypes[0].GetDiscriminatorProperty(), clientEval: false);

selectExpression.ApplyPredicate(
In(discriminatorColumn, Constant(concreteEntityTypes.Select(et => et.GetDiscriminatorValue()).ToList()), negated: false));
In((SqlExpression)discriminatorColumn, Constant(concreteEntityTypes.Select(et => et.GetDiscriminatorValue()).ToList()), negated: false));
}
}
}
157 changes: 0 additions & 157 deletions src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs

This file was deleted.

21 changes: 19 additions & 2 deletions src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage;
@@ -55,7 +56,7 @@ public virtual bool EnsureCreated()
{
created |= _cosmosClient.CreateContainerIfNotExists(
entityType.GetCosmosContainer(),
entityType.GetCosmosPartitionKeyStoreName());
GetCosmosPartitionKeyStoreName(entityType));
}

if (created)
@@ -89,7 +90,7 @@ public virtual async Task<bool> EnsureCreatedAsync(CancellationToken cancellatio
{
created |= await _cosmosClient.CreateContainerIfNotExistsAsync(
entityType.GetCosmosContainer(),
entityType.GetCosmosPartitionKeyStoreName(),
GetCosmosPartitionKeyStoreName(entityType),
cancellationToken);
}

@@ -145,5 +146,21 @@ public virtual bool CanConnect()
/// </summary>
public virtual Task<bool> CanConnectAsync(CancellationToken cancellationToken = default)
=> throw new NotImplementedException();

/// <summary>
/// Returns the store name of the property that is used to store the partition key.
/// </summary>
/// <param name="entityType"> The entity type to get the partition key property name for. </param>
/// <returns> The name of the partition key property. </returns>
private static string GetCosmosPartitionKeyStoreName([NotNull] IEntityType entityType)
{
var name = entityType.GetCosmosPartitionKeyPropertyName();
if (name != null)
{
return entityType.FindProperty(name).GetCosmosPropertyName();
}

return CosmosClientWrapper.DefaultPartitionKey;
}
}
}
10 changes: 2 additions & 8 deletions src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs
Original file line number Diff line number Diff line change
@@ -193,10 +193,7 @@ private bool Save(IUpdateEntry entry)

return _cosmosClient.CreateItem(collectionId, newDocument, GetPartitionKey(entry));
case EntityState.Modified:
var jObjectProperty = entityType.FindProperty(StoreKeyConvention.JObjectPropertyName);
var document = jObjectProperty != null
? (JObject)(entry.SharedIdentityEntry ?? entry).GetCurrentValue(jObjectProperty)
: null;
var document = documentSource.GetCurrentDocument(entry);
if (document != null)
{
if (documentSource.UpdateDocument(document, entry) == null)
@@ -247,10 +244,7 @@ private Task<bool> SaveAsync(IUpdateEntry entry, CancellationToken cancellationT
var newDocument = documentSource.CreateDocument(entry);
return _cosmosClient.CreateItemAsync(collectionId, newDocument, GetPartitionKey(entry), cancellationToken);
case EntityState.Modified:
var jObjectProperty = entityType.FindProperty(StoreKeyConvention.JObjectPropertyName);
var document = jObjectProperty != null
? (JObject)(entry.SharedIdentityEntry ?? entry).GetCurrentValue(jObjectProperty)
: null;
var document = documentSource.GetCurrentDocument(entry);
if (document != null)
{
if (documentSource.UpdateDocument(document, entry) == null)
87 changes: 53 additions & 34 deletions src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ public class DocumentSource
private readonly string _collectionId;
private readonly CosmosDatabaseWrapper _database;
private readonly IProperty _idProperty;
private readonly IProperty _jObjectProperty;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -35,6 +36,7 @@ public DocumentSource(IEntityType entityType, CosmosDatabaseWrapper database)
_collectionId = entityType.GetCosmosContainer();
_database = database;
_idProperty = entityType.FindProperty(StoreKeyConvention.IdPropertyName);
_jObjectProperty = entityType.FindProperty(StoreKeyConvention.JObjectPropertyName);
}

/// <summary>
@@ -71,39 +73,43 @@ public virtual JObject CreateDocument(IUpdateEntry entry)
{
document[storeName] = ConvertPropertyValue(property, entry.GetCurrentValue(property));
}
else if (entry.HasTemporaryValue(property))
{
((InternalEntityEntry)entry)[property] = entry.GetCurrentValue(property);
}
}

foreach (var ownedNavigation in entry.EntityType.GetNavigations())
foreach (var embeddedNavigation in entry.EntityType.GetNavigations())
{
var fk = ownedNavigation.ForeignKey;
var fk = embeddedNavigation.ForeignKey;
if (!fk.IsOwnership
|| ownedNavigation.IsDependentToPrincipal()
|| embeddedNavigation.IsDependentToPrincipal()
|| fk.DeclaringEntityType.IsDocumentRoot())
{
continue;
}

var nestedValue = entry.GetCurrentValue(ownedNavigation);
var nestedPropertyName = fk.DeclaringEntityType.GetCosmosContainingPropertyName();
if (nestedValue == null)
var embeddedValue = entry.GetCurrentValue(embeddedNavigation);
var embeddedPropertyName = fk.DeclaringEntityType.GetCosmosContainingPropertyName();
if (embeddedValue == null)
{
document[nestedPropertyName] = null;
document[embeddedPropertyName] = null;
}
else if (fk.IsUnique)
{
var dependentEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(nestedValue, fk.DeclaringEntityType);
document[nestedPropertyName] = _database.GetDocumentSource(dependentEntry.EntityType).CreateDocument(dependentEntry);
var dependentEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(embeddedValue, fk.DeclaringEntityType);
document[embeddedPropertyName] = _database.GetDocumentSource(dependentEntry.EntityType).CreateDocument(dependentEntry);
}
else
{
var array = new JArray();
foreach (var dependent in (IEnumerable)nestedValue)
foreach (var dependent in (IEnumerable)embeddedValue)
{
var dependentEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(dependent, fk.DeclaringEntityType);
array.Add(_database.GetDocumentSource(dependentEntry.EntityType).CreateDocument(dependentEntry));
}

document[nestedPropertyName] = array;
document[embeddedPropertyName] = array;
}
}

@@ -130,6 +136,10 @@ public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)
document[storeName] = ConvertPropertyValue(property, entry.GetCurrentValue(property));
anyPropertyUpdated = true;
}
else if (entry.HasTemporaryValue(property))
{
((InternalEntityEntry)entry)[property] = entry.GetCurrentValue(property);
}
}
}

@@ -143,62 +153,71 @@ public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)
continue;
}

var nestedDocumentSource = _database.GetDocumentSource(fk.DeclaringEntityType);
var nestedValue = entry.GetCurrentValue(ownedNavigation);
var nestedPropertyName = fk.DeclaringEntityType.GetCosmosContainingPropertyName();
if (nestedValue == null)
var embeddedDocumentSource = _database.GetDocumentSource(fk.DeclaringEntityType);
var embeddedValue = entry.GetCurrentValue(ownedNavigation);
var embeddedPropertyName = fk.DeclaringEntityType.GetCosmosContainingPropertyName();
if (embeddedValue == null)
{
if (document[nestedPropertyName] != null)
if (document[embeddedPropertyName] != null)
{
document[nestedPropertyName] = null;
document[embeddedPropertyName] = null;
anyPropertyUpdated = true;
}
}
else if (fk.IsUnique)
{
var nestedEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(nestedValue, fk.DeclaringEntityType);
if (nestedEntry == null)
var embeddedEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(embeddedValue, fk.DeclaringEntityType);
if (embeddedEntry == null)
{
return document;
}

if (document[nestedPropertyName] is JObject nestedDocument)
{
nestedDocument = nestedDocumentSource.UpdateDocument(nestedDocument, nestedEntry);
}
else
{
nestedDocument = nestedDocumentSource.CreateDocument(nestedEntry);
}
var embeddedDocument = embeddedDocumentSource.GetCurrentDocument(embeddedEntry);
embeddedDocument = embeddedDocument != null
? embeddedDocumentSource.UpdateDocument(embeddedDocument, embeddedEntry)
: embeddedDocumentSource.CreateDocument(embeddedEntry);

if (nestedDocument != null)
if (embeddedDocument != null)
{
document[nestedPropertyName] = nestedDocument;
document[embeddedPropertyName] = embeddedDocument;
anyPropertyUpdated = true;
}
}
else
{
var array = new JArray();
foreach (var dependent in (IEnumerable)nestedValue)
foreach (var dependent in (IEnumerable)embeddedValue)
{
var dependentEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(dependent, fk.DeclaringEntityType);
if (dependentEntry == null)
var embeddedEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(dependent, fk.DeclaringEntityType);
if (embeddedEntry == null)
{
continue;
}

array.Add(_database.GetDocumentSource(dependentEntry.EntityType).CreateDocument(dependentEntry));
var embeddedDocument = embeddedDocumentSource.GetCurrentDocument(embeddedEntry);
embeddedDocument = embeddedDocument != null
? embeddedDocumentSource.UpdateDocument(embeddedDocument, embeddedEntry) ?? embeddedDocument
: embeddedDocumentSource.CreateDocument(embeddedEntry);

array.Add(embeddedDocument);
}

document[nestedPropertyName] = array;
document[embeddedPropertyName] = array;
anyPropertyUpdated = true;
}
}

return anyPropertyUpdated ? document : null;
}

public virtual JObject GetCurrentDocument(IUpdateEntry entry)
{
var document = _jObjectProperty != null
? (JObject)(entry.SharedIdentityEntry ?? entry).GetCurrentValue(_jObjectProperty)
: null;
return document;
}

private static JToken ConvertPropertyValue(IProperty property, object value)
{
if (value == null)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Microsoft.EntityFrameworkCore.ValueGeneration.Internal;

namespace Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal
{
public class CosmosValueGeneratorSelector : ValueGeneratorSelector
{
public CosmosValueGeneratorSelector(ValueGeneratorSelectorDependencies dependencies)
: base(dependencies)
{
}

public override ValueGenerator Create(IProperty property, IEntityType entityType)
{
var type = property.ClrType.UnwrapNullableType().UnwrapEnumType();

if (property.GetCosmosPropertyName() == ""
&& type == typeof(int))
{
return new TemporaryIntValueGenerator();
}

return base.Create(property, entityType);
}
}
}
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
using System.Collections;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Microsoft.EntityFrameworkCore.ValueGeneration;
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Internal;

namespace Microsoft.EntityFrameworkCore.Query
{
@@ -62,10 +61,11 @@ private static void IncludeReference<TEntity, TIncludingEntity, TIncludedEntity>
else
{
SetIsLoadedNoTracking(includingEntity, navigation);
if (relatedEntity is object)
if (relatedEntity != null)
{
fixup(includingEntity, relatedEntity);
if (inverseNavigation != null && !inverseNavigation.IsCollection())
if (inverseNavigation != null
&& !inverseNavigation.IsCollection())
{
SetIsLoadedNoTracking(relatedEntity, inverseNavigation);
}
@@ -122,7 +122,7 @@ private static void PopulateCollection<TCollection, TElement, TRelatedEntity>(
}

if (StructuralComparisons.StructuralEqualityComparer.Equals(
innerKey, collectionMaterializationContext.SelfIdentifier))
innerKey, collectionMaterializationContext.SelfIdentifier))
{
// We don't need to materialize this entity but we may need to populate inner collections if any.
innerShaper(queryContext, dbDataReader, (TRelatedEntity)collectionMaterializationContext.Current, resultCoordinator);
@@ -184,10 +184,11 @@ private static void PopulateIncludeCollection<TIncludingEntity, TIncludedEntity>
}

if (StructuralComparisons.StructuralEqualityComparer.Equals(
innerKey, collectionMaterializationContext.SelfIdentifier))
innerKey, collectionMaterializationContext.SelfIdentifier))
{
// We don't need to materialize this entity but we may need to populate inner collections if any.
innerShaper(queryContext, dbDataReader, (TIncludedEntity)collectionMaterializationContext.Current, resultCoordinator);
innerShaper(
queryContext, dbDataReader, (TIncludedEntity)collectionMaterializationContext.Current, resultCoordinator);

return;
}
@@ -198,7 +199,8 @@ private static void PopulateIncludeCollection<TIncludingEntity, TIncludedEntity>
if (!trackingQuery)
{
fixup(entity, relatedEntity);
if (inverseNavigation != null && !inverseNavigation.IsCollection())
if (inverseNavigation != null
&& !inverseNavigation.IsCollection())
{
SetIsLoadedNoTracking(relatedEntity, inverseNavigation);
}
@@ -260,7 +262,7 @@ private static TCollection InitializeCollection<TElement, TCollection>(
Func<QueryContext, DbDataReader, object[]> parentIdentifier,
Func<QueryContext, DbDataReader, object[]> outerIdentifier,
IClrCollectionAccessor clrCollectionAccessor)
where TCollection :class, IEnumerable<TElement>
where TCollection : class, IEnumerable<TElement>
{
var collection = clrCollectionAccessor?.Create() ?? new List<TElement>();

@@ -286,7 +288,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
if (extensionExpression is IncludeExpression includeExpression)
{
Debug.Assert(!includeExpression.Navigation.IsCollection(),
Debug.Assert(
!includeExpression.Navigation.IsCollection(),
"Only reference include should be present in tree");
var entityClrType = includeExpression.EntityExpression.Type;
var includingClrType = includeExpression.Navigation.DeclaringEntityType.ClrType;
@@ -307,7 +310,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
Expression.Constant(includeExpression.Navigation),
Expression.Constant(inverseNavigation, typeof(INavigation)),
Expression.Constant(
GenerateFixup(includingClrType, relatedEntityClrType, includeExpression.Navigation, inverseNavigation).Compile()),
GenerateFixup(
includingClrType, relatedEntityClrType, includeExpression.Navigation, inverseNavigation).Compile()),
Expression.Constant(_tracking));
}

@@ -365,7 +369,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
collectionInitializingExpression.OuterIdentifier,
QueryCompilationContext.QueryContextParameter,
_dbDataReaderParameter).Compile()),
Expression.Constant(collectionInitializingExpression.Navigation?.GetCollectionAccessor(),
Expression.Constant(
collectionInitializingExpression.Navigation?.GetCollectionAccessor(),
typeof(IClrCollectionAccessor)));
}

@@ -403,7 +408,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
Expression.Constant(((LambdaExpression)Visit(collectionShaper.InnerShaper)).Compile()),
Expression.Constant(inverseNavigation, typeof(INavigation)),
Expression.Constant(
GenerateFixup(entityClrType, relatedEntityClrType, collectionShaper.Navigation, inverseNavigation).Compile()),
GenerateFixup(
entityClrType, relatedEntityClrType, collectionShaper.Navigation, inverseNavigation).Compile()),
Expression.Constant(_tracking));
}

@@ -468,22 +474,18 @@ private static Expression AssignReferenceNavigation(
ParameterExpression entity,
ParameterExpression relatedEntity,
INavigation navigation)
{
return entity.MakeMemberAccess(navigation.GetMemberInfo(forConstruction: false, forSet: true))
=> entity.MakeMemberAccess(navigation.GetMemberInfo(forConstruction: false, forSet: true))
.CreateAssignExpression(relatedEntity);
}

private static Expression AddToCollectionNavigation(
ParameterExpression entity,
ParameterExpression relatedEntity,
INavigation navigation)
{
return Expression.Call(
=> Expression.Call(
Expression.Constant(navigation.GetCollectionAccessor()),
_collectionAccessorAddMethodInfo,
entity,
relatedEntity);
}

private static readonly MethodInfo _collectionAccessorAddMethodInfo
= typeof(IClrCollectionAccessor).GetTypeInfo()
Original file line number Diff line number Diff line change
@@ -19,16 +19,16 @@ private class FromSqlParameterApplyingExpressionVisitor : ExpressionVisitor
private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpressions
= new Dictionary<FromSqlExpression, Expression>(ReferenceEqualityComparer.Instance);

private readonly ISqlExpressionFactory _SqlExpressionFactory;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly ParameterNameGenerator _parameterNameGenerator;
private readonly IReadOnlyDictionary<string, object> _parametersValues;

public FromSqlParameterApplyingExpressionVisitor(
ISqlExpressionFactory _sqlExpressionFactory,
ISqlExpressionFactory sqlExpressionFactory,
ParameterNameGenerator parameterNameGenerator,
IReadOnlyDictionary<string, object> parametersValues)
{
_SqlExpressionFactory = _sqlExpressionFactory;
_sqlExpressionFactory = sqlExpressionFactory;
_parameterNameGenerator = parameterNameGenerator;
_parametersValues = parametersValues;
}
@@ -67,7 +67,7 @@ public override Expression Visit(Expression expression)
new TypeMappedRelationalParameter(
parameterName,
parameterName,
_SqlExpressionFactory.GetTypeMappingForValue(parameterValues[i]),
_sqlExpressionFactory.GetTypeMappingForValue(parameterValues[i]),
parameterValues[i]?.GetType().IsNullableType()));
}
}
Original file line number Diff line number Diff line change
@@ -32,7 +32,6 @@ public SelectExpression Optimize(SelectExpression selectExpression, IReadOnlyDic
_parameterNameGeneratorFactory.Create(),
parametersValues).Visit(query);


return (SelectExpression)query;
}
}
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -86,7 +87,7 @@ public virtual SqlExpression TranslateAverage(Expression expression)
return inputType == typeof(float)
? _sqlExpressionFactory.Convert(
_sqlExpressionFactory.Function(
"AVG", new[] { sqlExpression }, typeof(double), null),
"AVG", new[] { sqlExpression }, typeof(double)),
sqlExpression.Type,
sqlExpression.TypeMapping)
: (SqlExpression)_sqlExpressionFactory.Function(
6 changes: 3 additions & 3 deletions src/EFCore/Query/ExpressionPrinter.cs
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ public virtual ExpressionPrinter DecrementIndent()

private void Append([NotNull] string message) => _stringBuilder.Append(message);

private void AppendLine([NotNull] string message = "")
private void AppendLine([NotNull] string message)
{
if (RemoveFormatting)
{
@@ -467,7 +467,7 @@ protected override Expression VisitLambda<T>(Expression<T> lambdaExpression)

foreach (var parameter in lambdaExpression.Parameters)
{
var parameterName = parameter.Name ?? parameter.ToString();
var parameterName = parameter.Name;

if (!_parametersInScope.ContainsKey(parameter))
{
@@ -478,7 +478,7 @@ protected override Expression VisitLambda<T>(Expression<T> lambdaExpression)

if (parameter != lambdaExpression.Parameters.Last())
{
_stringBuilder.Append(" | ");
_stringBuilder.Append(", ");
}
}

Original file line number Diff line number Diff line change
@@ -234,10 +234,7 @@ public static Expression CreateKeyAccessExpression(
AnonymousObject.AnonymousObjectCtor,
Expression.NewArrayInit(
typeof(object),
properties
.Select(p => Expression.Convert(CreatePropertyExpression(target, p, addNullCheck), typeof(object)))
.Cast<Expression>()
.ToArray()));
properties.Select(p => Expression.Convert(CreatePropertyExpression(target, p, addNullCheck), typeof(object)))));

private static Expression CreatePropertyExpression(Expression target, IProperty property, bool addNullCheck)
{
Original file line number Diff line number Diff line change
@@ -80,8 +80,7 @@ public virtual async Task Can_update_owner_with_dependents()
}
}

// #12086
//[ConditionalFact]
[ConditionalFact]
public virtual async Task Can_add_collection_dependent_to_owner()
{
await using (var testDatabase = CreateTestStore())
@@ -113,29 +112,21 @@ public virtual async Task Can_add_collection_dependent_to_owner()
addedAddress2 = new Address { Street = "Another", City = "Village" };
people[1].Addresses.Add(addedAddress2);

// Remove when issues #13578 and #13579 are fixed
var existingEntry = context.Attach(people[1].Addresses.First());
var existingAddressEntry = context.Entry(people[1].Addresses.First());

var secondPersonEntry = context.Entry(people[1]);
var json = secondPersonEntry.Property<JObject>("__jObject").CurrentValue;
var addressJson = existingAddressEntry.Property<JObject>("__jObject").CurrentValue;

var addresses = (JArray)json["Stored Addresses"];
var jsonAddress = (JObject)addresses[0];
Assert.Equal("Second", jsonAddress[nameof(Address.Street)]);
jsonAddress["unmappedId"] = 2;
Assert.Equal("Second", addressJson[nameof(Address.Street)]);
addressJson["unmappedId"] = 2;

secondPersonEntry.Property<JObject>("__jObject").CurrentValue = json;
existingAddressEntry.Property<JObject>("__jObject").CurrentValue = addressJson;

addedAddress3 = new Address { Street = "Another", City = "City" };
var existingLastAddress = people[2].Addresses.Last();
people[2].Addresses.Remove(existingLastAddress);
people[2].Addresses.Add(addedAddress3);
people[2].Addresses.Add(existingLastAddress);

// Remove when issues #13578 and #13579 are fixed
context.Attach(people[2].Addresses.First());
context.Attach(existingLastAddress);

context.SaveChanges();
}

@@ -155,12 +146,13 @@ public virtual async Task Can_add_collection_dependent_to_owner()
Assert.Equal(addedAddress2.Street, addresses[1].Street);
Assert.Equal(addedAddress2.City, addresses[1].City);

var json = context.Entry(people[1]).Property<JObject>("__jObject").CurrentValue;
var jsonAddress = (JObject)((JArray)json["Stored Addresses"])[0];
Assert.Equal("Second", jsonAddress[nameof(Address.Street)]);
// Uncomment when issue #13578 is fixed
//Assert.Equal(2, jsonAddress["unmappedId"]);
//Assert.Equal(2, jsonAddress.Count);
var existingAddressEntry = context.Entry(people[1].Addresses.First());

var addressJson = existingAddressEntry.Property<JObject>("__jObject").CurrentValue;

Assert.Equal("Second", addressJson[nameof(Address.Street)]);
Assert.Equal(4, addressJson.Count);
Assert.Equal(2, addressJson["unmappedId"]);

addresses = people[2].Addresses.ToList();
Assert.Equal(3, addresses.Count);
@@ -184,7 +176,8 @@ public virtual async Task Can_query_just_nested_reference()
{
using (var context = CreateContext())
{
var firstOperator = context.Set<Vehicle>().Select(v => v.Operator).OrderBy(o => o.VehicleName).AsNoTracking().First();
var firstOperator = context.Set<Vehicle>().OrderBy(o => o.Name).Select(v => v.Operator)
.AsNoTracking().First();

Assert.Equal("Albert Williams", firstOperator.Name);
Assert.Null(firstOperator.Vehicle);
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
using Xunit;
using Xunit.Abstractions;

0 comments on commit 8f50ca5

Please sign in to comment.