diff --git a/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs
index 278584d4faa..30a90f42958 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs
@@ -23,7 +23,7 @@ public static class CosmosDbContextOptionsExtensions
/// The account end-point to connect to.
/// The account key.
/// The database name.
- /// An optional action to allow additional Cosmos-specific configuration.
+ /// An optional action to allow additional Cosmos-specific configuration.
/// The options builder so that further configuration can be chained.
public static DbContextOptionsBuilder UseCosmos(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
@@ -46,7 +46,7 @@ public static DbContextOptionsBuilder UseCosmos(
/// The account end-point to connect to.
/// The account key.
/// The database name.
- /// An optional action to allow additional Cosmos-specific configuration.
+ /// An optional action to allow additional Cosmos-specific configuration.
/// The options builder so that further configuration can be chained.
public static DbContextOptionsBuilder UseCosmos(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
diff --git a/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs
index 39e156405a3..d09996dc4d5 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs
@@ -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
/// The property name used when targeting Cosmos.
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;
+ }
///
/// Sets the property name used when targeting Cosmos.
diff --git a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs
index b2cdd34dbd9..1b84cc4827b 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs
@@ -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()
.TryAdd()
.TryAdd()
+ .TryAdd()
.TryAdd()
.TryAdd()
.TryAdd()
diff --git a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs
index 51f8af767cb..46a95560336 100644
--- a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs
+++ b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs
@@ -9,15 +9,16 @@
using Microsoft.EntityFrameworkCore.Utilities;
using Newtonsoft.Json.Linq;
+// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
///
///
/// A convention that adds the 'id' property - a key required by Azure Cosmos.
///
- /// This convention also add the '__jObject' containing the JSON object returned by the store.
///
- ///
+ /// This convention also adds the '__jObject' containing the JSON object returned by the store.
+ ///
///
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)
{
diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs
index 88ded509eef..03f5a1006c1 100644
--- a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs
+++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs
@@ -1,8 +1,6 @@
// 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 JetBrains.Annotations;
-using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
@@ -25,22 +23,5 @@ public static bool IsDocumentRoot(this IEntityType entityType)
=> entityType.BaseType?.IsDocumentRoot()
?? (!entityType.IsOwned()
|| entityType[CosmosAnnotationNames.ContainerName] != null);
-
- ///
- /// 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.
- ///
- public static string GetCosmosPartitionKeyStoreName([NotNull] this IEntityType entityType)
- {
- var name = entityType.GetCosmosPartitionKeyPropertyName();
- if (name != null)
- {
- return entityType.FindProperty(name).GetCosmosPropertyName();
- }
-
- return CosmosClientWrapper.DefaultPartitionKey;
- }
}
}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs
index 1e5b06aa28a..f91ee0e40eb 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs
@@ -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())
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
index 868f877b5c3..e46a6684fd0 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@@ -185,18 +186,25 @@ private static readonly MethodInfo _getItemMethodInfo
private static readonly MethodInfo _toObjectMethodInfo
= typeof(CosmosProjectionBindingRemovingExpressionVisitor).GetTypeInfo().GetRuntimeMethods()
.Single(mi => mi.Name == nameof(SafeToObject));
- private static readonly MethodInfo _isNullMethodInfo
- = typeof(CosmosProjectionBindingRemovingExpressionVisitor).GetTypeInfo().GetRuntimeMethods()
- .Single(mi => mi.Name == nameof(IsNull));
+ private static readonly MethodInfo _collectionAccessorAddMethodInfo
+ = typeof(IClrCollectionAccessor).GetTypeInfo()
+ .GetDeclaredMethod(nameof(IClrCollectionAccessor.Add));
+ private static readonly MethodInfo _collectionAccessorGetOrCreateMethodInfo
+ = typeof(IClrCollectionAccessor).GetTypeInfo()
+ .GetDeclaredMethod(nameof(IClrCollectionAccessor.GetOrCreate));
private readonly SelectExpression _selectExpression;
private readonly ParameterExpression _jObjectParameter;
private readonly bool _trackQueryResults;
- private readonly IDictionary _materializationContextBindings
- = new Dictionary();
+ private readonly IDictionary _materializationContextBindings
+ = new Dictionary();
private readonly IDictionary _projectionBindings
= new Dictionary();
+ private readonly IDictionary _ownerMappings
+ = new Dictionary();
+ private (IEntityType EntityType, ParameterExpression JObjectVariable) _ownerInfo;
+ private ParameterExpression _ordinalParameter;
public CosmosProjectionBindingRemovingExpressionVisitor(
SelectExpression selectExpression,
@@ -229,32 +237,38 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
projectionExpression = ((UnaryExpression)convertExpression.Operand).Operand;
}
- Expression accessExpression;
+ Expression innerAccessExpression;
if (projectionExpression is ObjectArrayProjectionExpression objectArrayProjectionExpression)
{
- accessExpression = objectArrayProjectionExpression.AccessExpression;
+ innerAccessExpression = objectArrayProjectionExpression.AccessExpression;
_projectionBindings[objectArrayProjectionExpression] = parameterExpression;
storeName ??= objectArrayProjectionExpression.Name;
}
else
{
var entityProjectionExpression = (EntityProjectionExpression)projectionExpression;
- _projectionBindings[entityProjectionExpression.AccessExpression] = parameterExpression;
+ var accessExpression = entityProjectionExpression.AccessExpression;
+ _projectionBindings[accessExpression] = parameterExpression;
storeName ??= entityProjectionExpression.Name;
- switch (entityProjectionExpression.AccessExpression)
+ if (_ownerInfo.EntityType != null)
+ {
+ _ownerMappings[accessExpression] = _ownerInfo;
+ }
+
+ switch (accessExpression)
{
case ObjectAccessExpression innerObjectAccessExpression:
- accessExpression = innerObjectAccessExpression.AccessExpression;
+ innerAccessExpression = innerObjectAccessExpression.AccessExpression;
break;
case RootReferenceExpression _:
- accessExpression = _jObjectParameter;
+ innerAccessExpression = _jObjectParameter;
break;
default:
throw new InvalidOperationException();
}
}
- var valueExpression = CreateGetStoreValueExpression(accessExpression, storeName, parameterExpression.Type);
+ var valueExpression = CreateGetValueExpression(innerAccessExpression, storeName, parameterExpression.Type);
return Expression.MakeBinary(ExpressionType.Assign, binaryExpression.Left, valueExpression);
}
@@ -275,8 +289,7 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
entityProjectionExpression = (EntityProjectionExpression)projection;
}
- _materializationContextBindings[parameterExpression]
- = _projectionBindings[entityProjectionExpression.AccessExpression];
+ _materializationContextBindings[parameterExpression] = entityProjectionExpression.AccessExpression;
var updatedExpression = Expression.New(newExpression.Constructor,
Expression.Constant(ValueBuffer.Empty),
@@ -339,7 +352,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
var projection = GetProjection(projectionBindingExpression);
- return CreateGetStoreValueExpression(
+ return CreateGetValueExpression(
_jObjectParameter,
projection.Alias,
projectionBindingExpression.Type, (projection.Expression as SqlExpression)?.TypeMapping);
@@ -362,12 +375,20 @@ protected override Expression VisitExtension(Expression extensionExpression)
}
var jArray = _projectionBindings[objectArrayProjection];
- var jObjectParameter = Expression.Parameter(typeof(JObject), jArray.Name + "object");
- var ordinalParameter = Expression.Parameter(typeof(int), "ordinal");
+ var jObjectParameter = Expression.Parameter(typeof(JObject), jArray.Name + "Object");
+ var ordinalParameter = Expression.Parameter(typeof(int), jArray.Name + "Ordinal");
_projectionBindings[objectArrayProjection.InnerProjection.AccessExpression] = jObjectParameter;
+ if (_ownerInfo.EntityType != null)
+ {
+ _ownerMappings[objectArrayProjection.InnerProjection.AccessExpression] = _ownerInfo;
+ }
+ var previousOrdinalParameter = _ordinalParameter;
+ _ordinalParameter = ordinalParameter;
var innerShaper = Visit(collectionShaperExpression.InnerShaper);
+ _ordinalParameter = previousOrdinalParameter;
+
return Expression.Call(
_selectMethodInfo.MakeGenericMethod(typeof(JObject), innerShaper.Type),
Expression.Call(
@@ -387,6 +408,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
// These are the expressions added by JObjectInjectingExpressionVisitor
var jObjectBlock = (BlockExpression)Visit(includeExpression.EntityExpression);
+ var jObjectVariable = jObjectBlock.Variables.Single(v => v.Type == typeof(JObject));
var jObjectCondition = (ConditionalExpression)jObjectBlock.Expressions[jObjectBlock.Expressions.Count - 1];
var shaperBlock = (BlockExpression)jObjectCondition.IfFalse;
@@ -403,9 +425,13 @@ protected override Expression VisitExtension(Expression extensionExpression)
var concreteEntityTypeVariable = shaperBlock.Variables.Single(v => v.Type == typeof(IEntityType));
var inverseNavigation = navigation.FindInverse();
var fixup = GenerateFixup(
- includingClrType, relatedEntityClrType, navigation, inverseNavigation)
- .Compile();
+ includingClrType, relatedEntityClrType, navigation, inverseNavigation);
+ var initialize = GenerateInitialize(includingClrType, navigation);
+
+ var previousOwner = _ownerInfo;
+ _ownerInfo = (navigation.DeclaringEntityType, jObjectVariable);
var navigationExpression = Visit(includeExpression.NavigationExpression);
+ _ownerInfo = previousOwner;
shaperExpressions.Add(Expression.Call(
includeMethod.MakeGenericMethod(includingClrType, relatedEntityClrType),
@@ -415,7 +441,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
navigationExpression,
Expression.Constant(navigation),
Expression.Constant(inverseNavigation, typeof(INavigation)),
- Expression.Constant(fixup)));
+ Expression.Constant(fixup),
+ Expression.Constant(initialize, typeof(Action<>).MakeGenericType(includingClrType))));
shaperExpressions.Add(instanceVariable);
shaperBlock = shaperBlock.Update(shaperBlock.Variables, shaperExpressions);
@@ -443,7 +470,8 @@ private static void IncludeReference(
TIncludedEntity relatedEntity,
INavigation navigation,
INavigation inverseNavigation,
- Action fixup)
+ Action fixup,
+ Action initialize)
{
if (entity == null
|| !navigation.DeclaringEntityType.IsAssignableFrom(entityType))
@@ -483,7 +511,8 @@ private static void IncludeCollection(
IEnumerable relatedEntities,
INavigation navigation,
INavigation inverseNavigation,
- Action fixup)
+ Action fixup,
+ Action initialize)
{
if (entity == null
|| !navigation.DeclaringEntityType.IsAssignableFrom(entityType))
@@ -496,24 +525,38 @@ private static void IncludeCollection(
var includingEntity = (TIncludingEntity)entity;
SetIsLoadedNoTracking(includingEntity, navigation);
- foreach (var relatedEntity in relatedEntities)
+ if (relatedEntities != null)
{
- fixup(includingEntity, relatedEntity);
- if (inverseNavigation != null)
+ foreach (var relatedEntity in relatedEntities)
{
- SetIsLoadedNoTracking(relatedEntity, inverseNavigation);
+ fixup(includingEntity, relatedEntity);
+ if (inverseNavigation != null)
+ {
+ SetIsLoadedNoTracking(relatedEntity, inverseNavigation);
+ }
}
}
+ else
+ {
+ initialize(includingEntity);
+ }
}
else
{
entry.SetIsLoaded(navigation);
- using (var enumerator = relatedEntities.GetEnumerator())
+ if (relatedEntities != null)
{
- while (enumerator.MoveNext())
+ using (var enumerator = relatedEntities.GetEnumerator())
{
+ while (enumerator.MoveNext())
+ {
+ }
}
}
+ else
+ {
+ initialize((TIncludingEntity)entity);
+ }
}
}
@@ -525,7 +568,7 @@ private static void SetIsLoadedNoTracking(object entity, INavigation navigation)
?.GetGetter().GetClrValue(entity))
?.SetLoaded(entity, navigation.Name);
- private static LambdaExpression GenerateFixup(
+ private static Delegate GenerateFixup(
Type entityType,
Type relatedEntityType,
INavigation navigation,
@@ -549,14 +592,36 @@ private static LambdaExpression GenerateFixup(
}
- return Expression.Lambda(Expression.Block(typeof(void), expressions), entityParameter, relatedEntityParameter);
+ return Expression.Lambda(Expression.Block(typeof(void), expressions), entityParameter, relatedEntityParameter)
+ .Compile();
+ }
+
+ private static Delegate GenerateInitialize(
+ Type entityType,
+ INavigation navigation)
+ {
+ if (!navigation.IsCollection())
+ {
+ return null;
+ }
+
+ var entityParameter = Expression.Parameter(entityType);
+
+ var getOrCreateExpression = Expression.Call(
+ Expression.Constant(navigation.GetCollectionAccessor()),
+ _collectionAccessorGetOrCreateMethodInfo,
+ entityParameter,
+ Expression.Constant(true));
+
+ return Expression.Lambda(Expression.Block(typeof(void), getOrCreateExpression), entityParameter)
+ .Compile();
}
private static Expression AssignReferenceNavigation(
ParameterExpression entity,
ParameterExpression relatedEntity,
INavigation navigation)
- => entity.MakeMemberAccess(navigation.GetMemberInfo(forMaterialization: false, forSet: true)).Assign(relatedEntity);
+ => entity.MakeMemberAccess(navigation.GetMemberInfo(forMaterialization: true, forSet: true)).Assign(relatedEntity);
private static Expression AddToCollectionNavigation(
ParameterExpression entity,
@@ -569,10 +634,6 @@ private static Expression AddToCollectionNavigation(
relatedEntity,
Expression.Constant(true));
- private static readonly MethodInfo _collectionAccessorAddMethodInfo
- = typeof(IClrCollectionAccessor).GetTypeInfo()
- .GetDeclaredMethod(nameof(IClrCollectionAccessor.Add));
-
private int GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
=> projectionBindingExpression.ProjectionMember != null
? (int)((ConstantExpression)_selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember)).Value
@@ -593,19 +654,59 @@ private Expression CreateGetValueExpression(
{
if (property.Name == StoreKeyConvention.JObjectPropertyName)
{
- return jObjectExpression;
+ return _projectionBindings[jObjectExpression];
}
var storeName = property.GetCosmosPropertyName();
if (storeName.Length == 0)
{
+ var entityType = property.DeclaringEntityType;
+ if (!entityType.IsDocumentRoot())
+ {
+ var ownership = entityType.FindOwnership();
+
+ if (ownership != null
+ && !ownership.IsUnique
+ && property.IsPrimaryKey()
+ && !property.IsForeignKey()
+ && property.ClrType == typeof(int))
+ {
+ return _ordinalParameter;
+ }
+
+ var principalProperty = property.FindFirstPrincipal();
+ if (principalProperty != null)
+ {
+ Expression ownerJObjectExpression = null;
+ if (_ownerMappings.TryGetValue(jObjectExpression, out var ownerInfo))
+ {
+ Debug.Assert(principalProperty.DeclaringEntityType.IsAssignableFrom(ownerInfo.EntityType));
+
+ ownerJObjectExpression = ownerInfo.JObjectVariable;
+ }
+ else if (jObjectExpression is RootReferenceExpression rootReferenceExpression)
+ {
+ ownerJObjectExpression = rootReferenceExpression;
+ }
+ else if (jObjectExpression is ObjectAccessExpression objectAccessExpression)
+ {
+ ownerJObjectExpression = objectAccessExpression.AccessExpression;
+ }
+
+ if (ownerJObjectExpression != null)
+ {
+ return CreateGetValueExpression(ownerJObjectExpression, principalProperty);
+ }
+ }
+ }
+
return Expression.Default(property.ClrType);
}
- return CreateGetStoreValueExpression(jObjectExpression, storeName, property.ClrType, property.GetTypeMapping());
+ return CreateGetValueExpression(jObjectExpression, storeName, property.ClrType, property.GetTypeMapping());
}
- private Expression CreateGetStoreValueExpression(
+ private Expression CreateGetValueExpression(
Expression jObjectExpression,
string storeName,
Type clrType,
@@ -618,20 +719,20 @@ private Expression CreateGetStoreValueExpression(
}
else if (jObjectExpression is RootReferenceExpression rootReferenceExpression)
{
- innerExpression = CreateGetStoreValueExpression(
+ innerExpression = CreateGetValueExpression(
_jObjectParameter, rootReferenceExpression.Alias, typeof(JObject));
}
else if (jObjectExpression is ObjectAccessExpression objectAccessExpression)
{
var innerAccessExpression = objectAccessExpression.AccessExpression;
- innerExpression = CreateGetStoreValueExpression(
+ innerExpression = CreateGetValueExpression(
innerAccessExpression, ((IAccessExpression)innerAccessExpression).Name, typeof(JObject));
}
- var jTokenExpression = Expression.Call(innerExpression, _getItemMethodInfo, Expression.Constant(storeName));
- Expression valueExpression;
+ var jTokenExpression = CreateReadJTokenExpression(innerExpression, storeName);
+ Expression valueExpression;
var converter = typeMapping?.Converter;
if (converter != null)
{
@@ -652,15 +753,6 @@ private Expression CreateGetStoreValueExpression(
valueExpression = ConvertJTokenToType(jTokenExpression, clrType);
}
- if (clrType.IsNullableType())
- {
- valueExpression =
- Expression.Condition(
- Expression.Call(_isNullMethodInfo, jTokenExpression),
- Expression.Default(valueExpression.Type),
- valueExpression);
- }
-
return valueExpression;
}
@@ -672,10 +764,7 @@ private static Expression ConvertJTokenToType(Expression jTokenExpression, Type
jTokenExpression);
private static T SafeToObject(JToken token)
- => token == null ? default : token.ToObject();
-
- private static bool IsNull(JToken token)
- => token == null || token.Type == JTokenType.Null;
+ => token == null || token.Type == JTokenType.Null ? default : token.ToObject();
}
private class QueryingEnumerable : IEnumerable
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs
index f62f73852e1..e22e7ad1225 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs
@@ -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
diff --git a/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs
index d4262aa885a..0162424df4a 100644
--- a/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs
@@ -19,10 +19,10 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
///
public class EntityProjectionExpression : Expression, IPrintable, IAccessExpression
{
- private readonly IDictionary _propertyExpressionsCache
- = new Dictionary();
- private readonly IDictionary _navigationExpressionsCache
- = new Dictionary();
+ private readonly IDictionary _propertyExpressionsCache
+ = new Dictionary();
+ private readonly IDictionary _navigationExpressionsCache
+ = new Dictionary();
///
/// 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.
///
- 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;
}
///
@@ -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.
///
- 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;
}
///
@@ -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.
///
- 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;
}
///
@@ -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.
///
- 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;
}
///
diff --git a/src/EFCore.Cosmos/Query/Internal/IShaper.cs b/src/EFCore.Cosmos/Query/Internal/IShaper.cs
deleted file mode 100644
index 9ef33b0330b..00000000000
--- a/src/EFCore.Cosmos/Query/Internal/IShaper.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.Linq.Expressions;
-
-namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
-{
- ///
- /// 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.
- ///
- public interface IShaper
- {
- ///
- /// 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.
- ///
- LambdaExpression CreateShaperLambda();
-
- ///
- /// 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.
- ///
- Type Type { get; }
- }
-}
diff --git a/src/EFCore.Cosmos/Query/Internal/ObjectAccessExpression.cs b/src/EFCore.Cosmos/Query/Internal/ObjectAccessExpression.cs
index 6bb460e498a..0e1c5b54fd6 100644
--- a/src/EFCore.Cosmos/Query/Internal/ObjectAccessExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/ObjectAccessExpression.cs
@@ -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;
}
+ ///
+ /// 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.
+ ///
+ public override ExpressionType NodeType => ExpressionType.Extension;
+
///
/// 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
diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
index 8c313beae33..693c468687c 100644
--- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
@@ -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));
}
}
}
diff --git a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs b/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs
deleted file mode 100644
index e595d685901..00000000000
--- a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-// 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.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Reflection;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Metadata.Conventions;
-using Microsoft.EntityFrameworkCore.Query;
-using Newtonsoft.Json.Linq;
-
-namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
-{
- ///
- /// 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.
- ///
- public static class ValueBufferFactoryFactory
- {
- private static readonly ParameterExpression _jObjectParameter = Expression.Parameter(typeof(JObject), "jObject");
-
- private static readonly MethodInfo _getItemMethodInfo
- = typeof(JObject).GetTypeInfo().GetRuntimeProperties()
- .Single(pi => pi.Name == "Item" && pi.GetIndexParameters()[0].ParameterType == typeof(string))
- .GetMethod;
-
- private static readonly MethodInfo _toObjectMethodInfo
- = typeof(ValueBufferFactoryFactory).GetTypeInfo().GetRuntimeMethods()
- .Single(mi => mi.Name == nameof(SafeToObject));
-
- private static readonly MethodInfo _isNullMethodInfo
- = typeof(ValueBufferFactoryFactory).GetTypeInfo().GetRuntimeMethods()
- .Single(mi => mi.Name == nameof(IsNull));
-
- ///
- /// 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.
- ///
- public static Expression> Create(List usedProperties)
- => Expression.Lambda>(
- Expression.NewArrayInit(
- typeof(object),
- usedProperties
- .Select(
- p =>
- CreateGetValueExpression(
- _jObjectParameter,
- p))),
- _jObjectParameter);
-
- private static Expression CreateGetValueExpression(
- Expression jObjectExpression,
- IProperty property)
- {
- if (property.Name == StoreKeyConvention.JObjectPropertyName)
- {
- return jObjectExpression;
- }
-
- var storeName = property.GetCosmosPropertyName();
- if (storeName.Length == 0)
- {
- var type = property.GetTypeMapping().Converter?.ProviderClrType
- ?? property.ClrType;
-
- Expression calculatedExpression = Expression.Default(type);
- return type.IsValueType
- ? Expression.Convert(calculatedExpression, typeof(object))
- : calculatedExpression;
- }
-
- var valueExpression = CreateGetStoreValueExpression(jObjectExpression, property, storeName);
- if (valueExpression.Type.IsValueType)
- {
- valueExpression = Expression.Convert(valueExpression, typeof(object));
- }
-
- return valueExpression;
- }
-
- ///
- /// 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.
- ///
- public static Expression CreateGetStoreValueExpression(Expression jObjectExpression, IProperty property, string storeName)
- {
- Expression valueExpression = Expression.Call(
- jObjectExpression,
- _getItemMethodInfo,
- Expression.Constant(storeName));
-
- var modelClrType = property.ClrType;
- var converter = property.GetTypeMapping().Converter;
- if (converter != null)
- {
- var nullableExpression = valueExpression;
-
- if (valueExpression.Type != converter.ProviderClrType)
- {
- valueExpression =
- Expression.Call(
- _toObjectMethodInfo.MakeGenericMethod(converter.ProviderClrType),
- valueExpression);
-
- if (converter.ProviderClrType.IsNullableType())
- {
- nullableExpression = valueExpression;
- }
- }
-
- valueExpression = ReplacingExpressionVisitor.Replace(
- converter.ConvertFromProviderExpression.Parameters.Single(),
- valueExpression,
- converter.ConvertFromProviderExpression.Body);
-
- if (valueExpression.Type != modelClrType)
- {
- valueExpression = Expression.Convert(valueExpression, modelClrType);
- }
-
- if (modelClrType.IsNullableType())
- {
- valueExpression =
- Expression.Condition(
- nullableExpression.Type == typeof(JToken)
- ? (Expression)Expression.Call(_isNullMethodInfo, nullableExpression)
- : Expression.Equal(nullableExpression, Expression.Constant(null, nullableExpression.Type)),
- Expression.Constant(null, modelClrType),
- valueExpression);
- }
- }
- else if (valueExpression.Type != modelClrType)
- {
- valueExpression =
- Expression.Call(
- _toObjectMethodInfo.MakeGenericMethod(modelClrType),
- valueExpression);
- }
-
- return valueExpression;
- }
-
- private static T SafeToObject(JToken token)
- => token == null ? default : token.ToObject();
-
- private static bool IsNull(JToken token)
- => token == null || token.Type == JTokenType.Null;
- }
-}
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
index de846e0801b..357ac4fc105 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
@@ -4,7 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;
@@ -55,7 +55,7 @@ public virtual bool EnsureCreated()
{
created |= _cosmosClient.CreateContainerIfNotExists(
entityType.GetCosmosContainer(),
- entityType.GetCosmosPartitionKeyStoreName());
+ GetCosmosPartitionKeyStoreName(entityType));
}
if (created)
@@ -89,7 +89,7 @@ public virtual async Task EnsureCreatedAsync(CancellationToken cancellatio
{
created |= await _cosmosClient.CreateContainerIfNotExistsAsync(
entityType.GetCosmosContainer(),
- entityType.GetCosmosPartitionKeyStoreName(),
+ GetCosmosPartitionKeyStoreName(entityType),
cancellationToken);
}
@@ -145,5 +145,21 @@ public virtual bool CanConnect()
///
public virtual Task CanConnectAsync(CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
+
+ ///
+ /// Returns the store name of the property that is used to store the partition key.
+ ///
+ /// The entity type to get the partition key property name for.
+ /// The name of the partition key property.
+ private static string GetCosmosPartitionKeyStoreName([NotNull] IEntityType entityType)
+ {
+ var name = entityType.GetCosmosPartitionKeyPropertyName();
+ if (name != null)
+ {
+ return entityType.FindProperty(name).GetCosmosPropertyName();
+ }
+
+ return CosmosClientWrapper.DefaultPartitionKey;
+ }
}
}
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs
index 6ae1ef3d95f..95b4e2a154e 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs
@@ -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 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)
diff --git a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
index 8ce1803bb31..55eb47bea39 100644
--- a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
+++ b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
@@ -23,6 +23,7 @@ public class DocumentSource
private readonly string _collectionId;
private readonly CosmosDatabaseWrapper _database;
private readonly IProperty _idProperty;
+ private readonly IProperty _jObjectProperty;
///
/// 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);
}
///
@@ -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,55 +153,56 @@ 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;
}
}
@@ -199,6 +210,14 @@ public virtual JObject UpdateDocument(JObject document, IUpdateEntry entry)
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)
diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs
new file mode 100644
index 00000000000..fdef196d1c0
--- /dev/null
+++ b/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs
index 68fe75bc8de..b0c3d7cc17d 100644
--- a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs
+++ b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs
@@ -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;
diff --git a/src/EFCore.Relational/Query/Internal/AsyncQueryingEnumerable.cs b/src/EFCore.Relational/Query/AsyncQueryingEnumerable.cs
similarity index 100%
rename from src/EFCore.Relational/Query/Internal/AsyncQueryingEnumerable.cs
rename to src/EFCore.Relational/Query/AsyncQueryingEnumerable.cs
diff --git a/src/EFCore.Relational/Query/IncludeCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/IncludeCompilingExpressionVisitor.cs
index 35d3b152095..2a1c6ec676c 100644
--- a/src/EFCore.Relational/Query/IncludeCompilingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/IncludeCompilingExpressionVisitor.cs
@@ -59,10 +59,11 @@ private static void IncludeReference
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);
}
@@ -119,7 +120,7 @@ private static void PopulateCollection(
}
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);
@@ -181,10 +182,11 @@ private static void PopulateIncludeCollection
}
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;
}
@@ -195,7 +197,8 @@ private static void PopulateIncludeCollection
if (!trackingQuery)
{
fixup(entity, relatedEntity);
- if (inverseNavigation != null && !inverseNavigation.IsCollection())
+ if (inverseNavigation != null
+ && !inverseNavigation.IsCollection())
{
SetIsLoadedNoTracking(relatedEntity, inverseNavigation);
}
@@ -257,7 +260,7 @@ private static TCollection InitializeCollection(
Func parentIdentifier,
Func outerIdentifier,
IClrCollectionAccessor clrCollectionAccessor)
- where TCollection :class, IEnumerable
+ where TCollection : class, IEnumerable
{
var collection = clrCollectionAccessor?.Create() ?? new List();
@@ -283,7 +286,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;
@@ -304,7 +308,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));
}
@@ -362,7 +367,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)));
}
@@ -400,7 +406,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));
}
@@ -473,14 +480,12 @@ private static Expression AddToCollectionNavigation(
ParameterExpression entity,
ParameterExpression relatedEntity,
INavigation navigation)
- {
- return Expression.Call(
+ => Expression.Call(
Expression.Constant(navigation.GetCollectionAccessor()),
_collectionAccessorAddMethodInfo,
entity,
relatedEntity,
Expression.Constant(true));
- }
private static readonly MethodInfo _collectionAccessorAddMethodInfo
= typeof(IClrCollectionAccessor).GetTypeInfo()
diff --git a/src/EFCore.Relational/Query/Internal/FromSqlParameterApplyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/FromSqlParameterApplyingExpressionVisitor.cs
index 2a651205d30..9fa72e0f1df 100644
--- a/src/EFCore.Relational/Query/Internal/FromSqlParameterApplyingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/Internal/FromSqlParameterApplyingExpressionVisitor.cs
@@ -19,16 +19,16 @@ private class FromSqlParameterApplyingExpressionVisitor : ExpressionVisitor
private readonly IDictionary _visitedFromSqlExpressions
= new Dictionary(ReferenceEqualityComparer.Instance);
- private readonly ISqlExpressionFactory _SqlExpressionFactory;
+ private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly ParameterNameGenerator _parameterNameGenerator;
private readonly IReadOnlyDictionary _parametersValues;
public FromSqlParameterApplyingExpressionVisitor(
- ISqlExpressionFactory _sqlExpressionFactory,
+ ISqlExpressionFactory sqlExpressionFactory,
ParameterNameGenerator parameterNameGenerator,
IReadOnlyDictionary 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()));
}
}
diff --git a/src/EFCore.Relational/Query/Internal/ParameterValueBasedSelectExpressionOptimizer.cs b/src/EFCore.Relational/Query/Internal/ParameterValueBasedSelectExpressionOptimizer.cs
index c734ffce4d5..c76120f07bd 100644
--- a/src/EFCore.Relational/Query/Internal/ParameterValueBasedSelectExpressionOptimizer.cs
+++ b/src/EFCore.Relational/Query/Internal/ParameterValueBasedSelectExpressionOptimizer.cs
@@ -32,7 +32,6 @@ public SelectExpression Optimize(SelectExpression selectExpression, IReadOnlyDic
_parameterNameGeneratorFactory.Create(),
parametersValues).Visit(query);
-
return (SelectExpression)query;
}
}
diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs
index f9b214adcd3..e4fa084e736 100644
--- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs
@@ -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(
diff --git a/src/EFCore/Query/ExpressionPrinter.cs b/src/EFCore/Query/ExpressionPrinter.cs
index 05d57675c5e..0aaa0f19b33 100644
--- a/src/EFCore/Query/ExpressionPrinter.cs
+++ b/src/EFCore/Query/ExpressionPrinter.cs
@@ -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(Expression 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(Expression lambdaExpression)
if (parameter != lambdaExpression.Parameters.Last())
{
- _stringBuilder.Append(" | ");
+ _stringBuilder.Append(", ");
}
}
diff --git a/src/EFCore/Query/NavigationExpansion/Internal/NavigationExpansionHelpers.cs b/src/EFCore/Query/NavigationExpansion/Internal/NavigationExpansionHelpers.cs
index e5e3ee3c91e..634652c860f 100644
--- a/src/EFCore/Query/NavigationExpansion/Internal/NavigationExpansionHelpers.cs
+++ b/src/EFCore/Query/NavigationExpansion/Internal/NavigationExpansionHelpers.cs
@@ -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()
- .ToArray()));
+ properties.Select(p => Expression.Convert(CreatePropertyExpression(target, p, addNullCheck), typeof(object)))));
private static Expression CreatePropertyExpression(Expression target, IProperty property, bool addNullCheck)
{
diff --git a/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs b/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs
similarity index 91%
rename from test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs
rename to test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs
index 264ea9982d0..60c98948cdd 100644
--- a/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs
@@ -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,18 +112,14 @@ 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").CurrentValue;
+ var addressJson = existingAddressEntry.Property("__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").CurrentValue = json;
+ existingAddressEntry.Property("__jObject").CurrentValue = addressJson;
addedAddress3 = new Address { Street = "Another", City = "City" };
var existingLastAddress = people[2].Addresses.Last();
@@ -132,10 +127,6 @@ public virtual async Task Can_add_collection_dependent_to_owner()
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").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").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().Select(v => v.Operator).OrderBy(o => o.VehicleName).AsNoTracking().First();
+ var firstOperator = context.Set().OrderBy(o => o.Name).Select(v => v.Operator)
+ .AsNoTracking().First();
Assert.Equal("Albert Williams", firstOperator.Name);
Assert.Null(firstOperator.Vehicle);
diff --git a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs
index 03847fdb35c..f459f300d04 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs
@@ -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;