diff --git a/src/EFCore.Abstractions/EFCore.Abstractions.csproj b/src/EFCore.Abstractions/EFCore.Abstractions.csproj
index 3b4b28fad6b..991c9218cc2 100644
--- a/src/EFCore.Abstractions/EFCore.Abstractions.csproj
+++ b/src/EFCore.Abstractions/EFCore.Abstractions.csproj
@@ -23,7 +23,6 @@
TextTemplatingFileGenerator
- Microsoft.EntityFrameworkCore.InternalAbstractionsStrings.Designer.cs
diff --git a/src/EFCore.Cosmos/CosmosCollectionOwnershipBuilderExtensions.cs b/src/EFCore.Cosmos/CosmosCollectionOwnershipBuilderExtensions.cs
new file mode 100644
index 00000000000..cde0c3844b0
--- /dev/null
+++ b/src/EFCore.Cosmos/CosmosCollectionOwnershipBuilderExtensions.cs
@@ -0,0 +1,53 @@
+// 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.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos
+{
+ ///
+ /// Cosmos specific extension methods for .
+ ///
+ public static class CosmosCollectionOwnershipBuilderExtensions
+ {
+ ///
+ /// Configures the container that the entity maps to when targeting Azure Cosmos.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the container.
+ /// The same builder instance so that multiple calls can be chained.
+ public static CollectionOwnershipBuilder ToContainer(
+ [NotNull] this CollectionOwnershipBuilder referenceOwnershipBuilder,
+ [CanBeNull] string name)
+ {
+ Check.NotNull(referenceOwnershipBuilder, nameof(referenceOwnershipBuilder));
+ Check.NullButNotEmpty(name, nameof(name));
+
+ referenceOwnershipBuilder.GetInfrastructure()
+ .Cosmos(ConfigurationSource.Explicit)
+ .ToContainer(name);
+
+ return referenceOwnershipBuilder;
+ }
+
+ ///
+ /// Configures the container that the entity maps to when targeting Azure Cosmos.
+ ///
+ /// The entity type being configured.
+ /// The entity type that this relationship targets.
+ /// The builder for the entity type being configured.
+ /// The name of the container.
+ /// The same builder instance so that multiple calls can be chained.
+ public static CollectionOwnershipBuilder ToContainer(
+ [NotNull] this CollectionOwnershipBuilder referenceOwnershipBuilder,
+ [CanBeNull] string name)
+ where TEntity : class
+ where TDependentEntity : class
+ => (CollectionOwnershipBuilder)ToContainer((CollectionOwnershipBuilder)referenceOwnershipBuilder, name);
+ }
+}
diff --git a/src/EFCore.Cosmos/CosmosEntityTypeBuilderExtensions.cs b/src/EFCore.Cosmos/CosmosEntityTypeBuilderExtensions.cs
new file mode 100644
index 00000000000..9ab7607750d
--- /dev/null
+++ b/src/EFCore.Cosmos/CosmosEntityTypeBuilderExtensions.cs
@@ -0,0 +1,52 @@
+// 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.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore
+{
+ ///
+ /// Cosmos specific extension methods for .
+ ///
+ public static class CosmosEntityTypeBuilderExtensions
+ {
+ ///
+ /// Configures the container that the entity maps to when targeting Azure Cosmos.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the container.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder ToContainer(
+ [NotNull] this EntityTypeBuilder entityTypeBuilder,
+ [CanBeNull] string name)
+ {
+ Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
+ Check.NullButNotEmpty(name, nameof(name));
+
+ entityTypeBuilder.GetInfrastructure()
+ .Cosmos(ConfigurationSource.Explicit)
+ .ToContainer(name);
+
+ return entityTypeBuilder;
+ }
+
+ ///
+ /// Configures the container that the entity maps to when targeting Azure Cosmos.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// The name of the container.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder ToContainer(
+ [NotNull] this EntityTypeBuilder entityTypeBuilder,
+ [CanBeNull] string name)
+ where TEntity : class
+ => (EntityTypeBuilder)ToContainer((EntityTypeBuilder)entityTypeBuilder, name);
+ }
+}
diff --git a/src/EFCore.Cosmos/CosmosModelBuilderExtensions.cs b/src/EFCore.Cosmos/CosmosModelBuilderExtensions.cs
new file mode 100644
index 00000000000..cda22460840
--- /dev/null
+++ b/src/EFCore.Cosmos/CosmosModelBuilderExtensions.cs
@@ -0,0 +1,34 @@
+// 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.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore
+{
+ public static class CosmosModelBuilderExtensions
+ {
+ ///
+ /// Configures the default container name that will be used if no name
+ /// is explicitly configured for an entity type.
+ ///
+ /// The model builder.
+ /// The default container name.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ModelBuilder HasDefaultContainerName(
+ [NotNull] this ModelBuilder modelBuilder,
+ [CanBeNull] string name)
+ {
+ Check.NotNull(modelBuilder, nameof(modelBuilder));
+ Check.NullButNotEmpty(name, nameof(name));
+
+ modelBuilder.GetInfrastructure().Cosmos(ConfigurationSource.Explicit).HasDefaultContainerName(name);
+
+ return modelBuilder;
+ }
+ }
+}
diff --git a/src/EFCore.Cosmos/CosmosReferenceOwnershipBuilderExtensions.cs b/src/EFCore.Cosmos/CosmosReferenceOwnershipBuilderExtensions.cs
new file mode 100644
index 00000000000..ed3064123cb
--- /dev/null
+++ b/src/EFCore.Cosmos/CosmosReferenceOwnershipBuilderExtensions.cs
@@ -0,0 +1,53 @@
+// 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.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos
+{
+ ///
+ /// Cosmos specific extension methods for .
+ ///
+ public static class CosmosReferenceOwnershipBuilderExtensions
+ {
+ ///
+ /// Configures the container that the entity maps to when targeting Azure Cosmos.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the container.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ReferenceOwnershipBuilder ToContainer(
+ [NotNull] this ReferenceOwnershipBuilder referenceOwnershipBuilder,
+ [CanBeNull] string name)
+ {
+ Check.NotNull(referenceOwnershipBuilder, nameof(referenceOwnershipBuilder));
+ Check.NullButNotEmpty(name, nameof(name));
+
+ referenceOwnershipBuilder.GetInfrastructure()
+ .Cosmos(ConfigurationSource.Explicit)
+ .ToContainer(name);
+
+ return referenceOwnershipBuilder;
+ }
+
+ ///
+ /// Configures the container that the entity maps to when targeting Azure Cosmos.
+ ///
+ /// The entity type being configured.
+ /// The entity type that this relationship targets.
+ /// The builder for the entity type being configured.
+ /// The name of the container.
+ /// The same builder instance so that multiple calls can be chained.
+ public static ReferenceOwnershipBuilder ToContainer(
+ [NotNull] this ReferenceOwnershipBuilder referenceOwnershipBuilder,
+ [CanBeNull] string name)
+ where TEntity : class
+ where TRelatedEntity : class
+ => (ReferenceOwnershipBuilder)ToContainer((ReferenceOwnershipBuilder)referenceOwnershipBuilder, name);
+ }
+}
diff --git a/src/EFCore.Cosmos/EFCore.Cosmos.csproj b/src/EFCore.Cosmos/EFCore.Cosmos.csproj
index 9ebd289733d..e8f1ad7381f 100644
--- a/src/EFCore.Cosmos/EFCore.Cosmos.csproj
+++ b/src/EFCore.Cosmos/EFCore.Cosmos.csproj
@@ -26,4 +26,30 @@
+
+
+ CosmosStrings.Designer.tt
+ True
+ True
+
+
+
+
+
+
+ TextTemplatingFileGenerator
+ CosmosStrings.Designer.cs
+
+
+
+
+
+
+
+
+
+ Microsoft.EntityFrameworkCore.Cosmos.Internal
+
+
+
diff --git a/src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs
index 5a0671fd251..dd03336d44f 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosMetadataExtensions.cs
@@ -11,6 +11,22 @@ namespace Microsoft.EntityFrameworkCore
///
public static class CosmosMetadataExtensions
{
+ ///
+ /// Gets the Cosmos-specific metadata for a model.
+ ///
+ /// The model to get metadata for.
+ /// The Cosmos-specific metadata for the model.
+ public static ICosmosModelAnnotations Cosmos(this IModel model)
+ => new CosmosModelAnnotations(model);
+
+ ///
+ /// Gets the Cosmos-specific metadata for a model.
+ ///
+ /// The model to get metadata for.
+ /// The Cosmos-specific metadata for the model.
+ public static CosmosModelAnnotations Cosmos(this IMutableModel model)
+ => (CosmosModelAnnotations)Cosmos((IModel)model);
+
///
/// Gets the Cosmos-specific metadata for an entity type.
///
diff --git a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs
index 6b61de339f4..47c666901da 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
// 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.Infrastructure;
using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Query.ExpressionVisitors.Internal;
@@ -28,6 +29,7 @@ public static IServiceCollection AddEntityFrameworkCosmos([NotNull] this IServic
.TryAdd()
.TryAdd()
.TryAdd()
+ .TryAdd()
.TryAdd()
.TryAdd()
.TryAdd()
@@ -35,7 +37,6 @@ public static IServiceCollection AddEntityFrameworkCosmos([NotNull] this IServic
.TryAdd()
.TryAdd()
.TryAdd()
- .TryAdd()
.TryAddProviderSpecificServices(
b => b
.TryAddScoped()
diff --git a/src/EFCore.Cosmos/Infrastructure/CosmosModelCustomizer.cs b/src/EFCore.Cosmos/Infrastructure/CosmosModelCustomizer.cs
new file mode 100644
index 00000000000..c3178020367
--- /dev/null
+++ b/src/EFCore.Cosmos/Infrastructure/CosmosModelCustomizer.cs
@@ -0,0 +1,47 @@
+// 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 Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Infrastructure
+{
+ ///
+ ///
+ /// Builds the model for a given context. This default implementation builds the model by calling
+ /// on the context.
+ ///
+ ///
+ /// This type is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ public class CosmosModelCustomizer : ModelCustomizer
+ {
+ public CosmosModelCustomizer(ModelCustomizerDependencies dependencies)
+ : base(dependencies)
+ {
+ }
+
+ ///
+ ///
+ /// Performs additional configuration of the model in addition to what is discovered by convention. This implementation
+ /// builds the model for a given context by calling
+ /// on the context.
+ ///
+ ///
+ ///
+ /// The builder being used to construct the model.
+ ///
+ ///
+ /// The context instance that the model is being created for.
+ ///
+ public override void Customize(ModelBuilder modelBuilder, DbContext context)
+ {
+ modelBuilder.GetInfrastructure().Cosmos(ConfigurationSource.Convention).HasDefaultContainerName(context.GetType().Name);
+
+ base.Customize(modelBuilder, context);
+ }
+ }
+}
diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSqlDbOptionExtension.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs
similarity index 100%
rename from src/EFCore.Cosmos/Infrastructure/Internal/CosmosSqlDbOptionExtension.cs
rename to src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs
diff --git a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs
index 58730ebf527..ebc7b5a1680 100644
--- a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs
+++ b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs
@@ -11,15 +11,17 @@ public class CosmosConventionSetBuilder : IConventionSetBuilder
public ConventionSet AddConventions(ConventionSet conventionSet)
{
var discriminatorConvention = new DiscriminatorConvention();
-
var storeKeyConvention = new StoreKeyConvention();
conventionSet.EntityTypeAddedConventions.Add(storeKeyConvention);
conventionSet.EntityTypeAddedConventions.Add(discriminatorConvention);
+ conventionSet.BaseEntityTypeChangedConventions.Add(storeKeyConvention);
conventionSet.BaseEntityTypeChangedConventions.Add(discriminatorConvention);
conventionSet.ForeignKeyOwnershipChangedConventions.Add(storeKeyConvention);
+ conventionSet.EntityTypeAnnotationChangedConventions.Add(storeKeyConvention);
+
return conventionSet;
}
}
diff --git a/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs
index a7b09f91ee3..e6b16a23720 100644
--- a/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs
+++ b/src/EFCore.Cosmos/Metadata/Conventions/Internal/StoreKeyConvention.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.ValueGeneration.Internal;
@@ -9,7 +10,11 @@
namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal
{
- public class StoreKeyConvention : IEntityTypeAddedConvention, IForeignKeyOwnershipChangedConvention
+ public class StoreKeyConvention :
+ IEntityTypeAddedConvention,
+ IForeignKeyOwnershipChangedConvention,
+ IEntityTypeAnnotationChangedConvention,
+ IBaseTypeChangedConvention
{
public static readonly string IdPropertyName = "id";
public static readonly string JObjectPropertyName = "__jObject";
@@ -25,33 +30,51 @@ public InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuild
var jObjectProperty = entityTypeBuilder.Property(JObjectPropertyName, typeof(JObject), ConfigurationSource.Convention);
}
-
- return entityTypeBuilder;
- }
-
- public InternalRelationshipBuilder Apply(InternalRelationshipBuilder relationshipBuilder)
- {
- if (relationshipBuilder.Metadata.IsOwnership)
+ else
{
- var ownedType = relationshipBuilder.Metadata.DeclaringEntityType;
- var idProperty = ownedType.FindProperty(IdPropertyName);
+ var entityType = entityTypeBuilder.Metadata;
+ var idProperty = entityType.FindDeclaredProperty(IdPropertyName);
if (idProperty != null)
{
- var key = ownedType.FindKey(idProperty);
+ var key = entityType.FindKey(idProperty);
if (key != null)
{
- ownedType.Builder.RemoveKey(key, ConfigurationSource.Convention);
+ entityType.Builder.RemoveKey(key, ConfigurationSource.Convention);
}
}
- var jObjectProperty = ownedType.FindProperty(JObjectPropertyName);
+ var jObjectProperty = entityType.FindDeclaredProperty(JObjectPropertyName);
if (jObjectProperty != null)
{
- ownedType.Builder.RemoveShadowPropertiesIfUnused(new[] { jObjectProperty });
+ entityType.Builder.RemoveShadowPropertiesIfUnused(new[] { jObjectProperty });
}
}
+ return entityTypeBuilder;
+ }
+
+ public InternalRelationshipBuilder Apply(InternalRelationshipBuilder relationshipBuilder)
+ {
+ Apply(relationshipBuilder.Metadata.DeclaringEntityType.Builder);
+
return relationshipBuilder;
}
+
+ public Annotation Apply(InternalEntityTypeBuilder entityTypeBuilder, string name, Annotation annotation, Annotation oldAnnotation)
+ {
+ if(name == CosmosAnnotationNames.ContainerName)
+ {
+ Apply(entityTypeBuilder);
+ }
+
+ return annotation;
+ }
+
+ public bool Apply(InternalEntityTypeBuilder entityTypeBuilder, EntityType oldBaseType)
+ {
+ Apply(entityTypeBuilder);
+
+ return true;
+ }
}
}
diff --git a/src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs b/src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs
index 0f5395b53ab..a65581911ad 100644
--- a/src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs
+++ b/src/EFCore.Cosmos/Metadata/CosmosEntityTypeAnnotations.cs
@@ -24,6 +24,9 @@ public CosmosEntityTypeAnnotations(IEntityType entityType)
protected virtual IEntityType EntityType => (IEntityType)Annotations.Metadata;
+ protected virtual CosmosModelAnnotations GetAnnotations(IModel model)
+ => new CosmosModelAnnotations(model);
+
protected virtual CosmosEntityTypeAnnotations GetAnnotations([NotNull] IEntityType entityType)
=> new CosmosEntityTypeAnnotations(entityType);
@@ -38,14 +41,13 @@ public virtual string ContainerName
set => SetContainerName(value);
}
- private static string GetDefaultContainerName() => "Unicorn";
+ private string GetDefaultContainerName() => GetAnnotations(EntityType.Model).DefaultContainerName
+ ?? EntityType.ShortName();
protected virtual bool SetContainerName([CanBeNull] string value)
- {
- return Annotations.SetAnnotation(
+ => Annotations.SetAnnotation(
CosmosAnnotationNames.ContainerName,
Check.NullButNotEmpty(value, nameof(value)));
- }
public virtual IProperty DiscriminatorProperty
{
diff --git a/src/EFCore.Cosmos/Metadata/CosmosModelAnnotations.cs b/src/EFCore.Cosmos/Metadata/CosmosModelAnnotations.cs
new file mode 100644
index 00000000000..27bd1736413
--- /dev/null
+++ b/src/EFCore.Cosmos/Metadata/CosmosModelAnnotations.cs
@@ -0,0 +1,37 @@
+// 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.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata
+{
+ public class CosmosModelAnnotations : ICosmosModelAnnotations
+ {
+ public CosmosModelAnnotations(IModel model)
+ : this(new CosmosAnnotations(model))
+ {
+ }
+
+ protected CosmosModelAnnotations(CosmosAnnotations annotations) => Annotations = annotations;
+
+ protected virtual CosmosAnnotations Annotations { get; }
+
+ protected virtual IModel Model => (IModel)Annotations.Metadata;
+
+ public virtual string DefaultContainerName
+ {
+ get => (string)Annotations.Metadata[CosmosAnnotationNames.ContainerName];
+
+ [param: CanBeNull]
+ set => SetDefaultContainerName(value);
+ }
+
+ protected virtual bool SetDefaultContainerName([CanBeNull] string value)
+ => Annotations.SetAnnotation(
+ CosmosAnnotationNames.ContainerName,
+ Check.NullButNotEmpty(value, nameof(value)));
+ }
+}
diff --git a/src/EFCore.Cosmos/Metadata/ICosmosModelAnnotations.cs b/src/EFCore.Cosmos/Metadata/ICosmosModelAnnotations.cs
new file mode 100644
index 00000000000..47b79283024
--- /dev/null
+++ b/src/EFCore.Cosmos/Metadata/ICosmosModelAnnotations.cs
@@ -0,0 +1,10 @@
+// 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.
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata
+{
+ public interface ICosmosModelAnnotations
+ {
+ string DefaultContainerName { get; }
+ }
+}
diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationsBuilder.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationsBuilder.cs
new file mode 100644
index 00000000000..0451d927a29
--- /dev/null
+++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationsBuilder.cs
@@ -0,0 +1,65 @@
+// 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.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class CosmosAnnotationsBuilder : CosmosAnnotations
+ {
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public CosmosAnnotationsBuilder(
+ [NotNull] InternalMetadataBuilder internalBuilder,
+ ConfigurationSource configurationSource)
+ : base(internalBuilder.Metadata)
+ {
+ MetadataBuilder = internalBuilder;
+ ConfigurationSource = configurationSource;
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual ConfigurationSource ConfigurationSource { get; }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual InternalMetadataBuilder MetadataBuilder { get; }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public override bool SetAnnotation(
+ string relationalAnnotationName,
+ object value)
+ => MetadataBuilder.HasAnnotation(relationalAnnotationName, value, ConfigurationSource);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public override bool CanSetAnnotation(
+ string relationalAnnotationName,
+ object value)
+ => MetadataBuilder.CanSetAnnotation(relationalAnnotationName, value, ConfigurationSource);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public override bool RemoveAnnotation(string annotationName)
+ => MetadataBuilder.RemoveAnnotation(annotationName, ConfigurationSource);
+ }
+}
diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeBuilderAnnotations.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeBuilderAnnotations.cs
new file mode 100644
index 00000000000..57a15dfeb3b
--- /dev/null
+++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeBuilderAnnotations.cs
@@ -0,0 +1,65 @@
+// 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.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
+{
+ public class CosmosEntityTypeBuilderAnnotations : CosmosEntityTypeAnnotations
+ {
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public CosmosEntityTypeBuilderAnnotations(
+ [NotNull] InternalEntityTypeBuilder internalBuilder,
+ ConfigurationSource configurationSource)
+ : base(new CosmosAnnotationsBuilder(internalBuilder, configurationSource))
+ {
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected new virtual CosmosAnnotationsBuilder Annotations => (CosmosAnnotationsBuilder)base.Annotations;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected virtual InternalEntityTypeBuilder EntityTypeBuilder => (InternalEntityTypeBuilder)Annotations.MetadataBuilder;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected override CosmosModelAnnotations GetAnnotations(IModel model)
+ => new CosmosModelBuilderAnnotations(
+ ((Model)model).Builder,
+ Annotations.ConfigurationSource);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected override CosmosEntityTypeAnnotations GetAnnotations(IEntityType entityType)
+ => new CosmosEntityTypeBuilderAnnotations(
+ ((EntityType)entityType).Builder,
+ Annotations.ConfigurationSource);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual bool ToContainer([CanBeNull] string name)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ return SetContainerName(name);
+ }
+ }
+}
diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosInternalMetadataBuilderExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosInternalMetadataBuilderExtensions.cs
new file mode 100644
index 00000000000..d0c80818340
--- /dev/null
+++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosInternalMetadataBuilderExtensions.cs
@@ -0,0 +1,33 @@
+// 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.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static class CosmosInternalMetadataBuilderExtensions
+ {
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static CosmosModelBuilderAnnotations Cosmos(
+ [NotNull] this InternalModelBuilder builder,
+ ConfigurationSource configurationSource)
+ => new CosmosModelBuilderAnnotations(builder, configurationSource);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static CosmosEntityTypeBuilderAnnotations Cosmos(
+ [NotNull] this InternalEntityTypeBuilder builder,
+ ConfigurationSource configurationSource)
+ => new CosmosEntityTypeBuilderAnnotations(builder, configurationSource);
+ }
+}
diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosModelBuilderAnnotations.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosModelBuilderAnnotations.cs
new file mode 100644
index 00000000000..5c573f132ae
--- /dev/null
+++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosModelBuilderAnnotations.cs
@@ -0,0 +1,45 @@
+// 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.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class CosmosModelBuilderAnnotations : CosmosModelAnnotations
+ {
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public CosmosModelBuilderAnnotations(
+ [NotNull] InternalModelBuilder internalBuilder,
+ ConfigurationSource configurationSource)
+ : base(new CosmosAnnotationsBuilder(internalBuilder, configurationSource))
+ {
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected new virtual CosmosAnnotationsBuilder Annotations => (CosmosAnnotationsBuilder)base.Annotations;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected virtual InternalModelBuilder ModelBuilder => (InternalModelBuilder)Annotations.MetadataBuilder;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual bool HasDefaultContainerName([CanBeNull] string name) => SetDefaultContainerName(name);
+ }
+}
diff --git a/src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs
index 0a3985c6962..940adbf316c 100644
--- a/src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs
+++ b/src/EFCore.Cosmos/Metadata/Internal/EntityTypeExtensions.cs
@@ -8,6 +8,9 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
public static class EntityTypeExtensions
{
public static bool IsDocumentRoot(this IEntityType entityType)
- => !entityType.IsOwned();
+ => entityType.BaseType == null
+ ? !entityType.IsOwned()
+ || entityType[CosmosAnnotationNames.ContainerName] != null
+ : entityType.BaseType.IsDocumentRoot();
}
}
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
new file mode 100644
index 00000000000..e9756e83e52
--- /dev/null
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
@@ -0,0 +1,62 @@
+//
+
+using System;
+using System.Reflection;
+using System.Resources;
+using JetBrains.Annotations;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static class CosmosStrings
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.EntityFrameworkCore.Cosmos.Properties.CosmosStrings", typeof(CosmosStrings).GetTypeInfo().Assembly);
+
+ ///
+ /// The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.
+ ///
+ public static string OrphanedNestedDocument([CanBeNull] object entityType, [CanBeNull] object missingEntityType)
+ => string.Format(
+ GetString("OrphanedNestedDocument", nameof(entityType), nameof(missingEntityType)),
+ entityType, missingEntityType);
+
+ ///
+ /// The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the key value '{keyValue}'.
+ ///
+ public static string OrphanedNestedDocumentSensitive([CanBeNull] object entityType, [CanBeNull] object missingEntityType, [CanBeNull] object keyValue)
+ => string.Format(
+ GetString("OrphanedNestedDocumentSensitive", nameof(entityType), nameof(missingEntityType), nameof(keyValue)),
+ entityType, missingEntityType, keyValue);
+
+ ///
+ /// The entity of type '{entityType}' cannot be queried directly because it is mapped as a part of the document mapped to '{principalEntityType}'. Rewrite the query to start with '{principalEntityType}'.
+ ///
+ public static string QueryRootNestedEntityType([CanBeNull] object entityType, [CanBeNull] object principalEntityType)
+ => string.Format(
+ GetString("QueryRootNestedEntityType", nameof(entityType), nameof(principalEntityType)),
+ entityType, principalEntityType);
+
+ ///
+ /// No matching discriminator values where found for this instance of '{entityType}'.
+ ///
+ public static string UnableToDiscriminate([CanBeNull] object entityType)
+ => string.Format(
+ GetString("UnableToDiscriminate", nameof(entityType)),
+ entityType);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.tt b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.tt
new file mode 100644
index 00000000000..702ca5eade1
--- /dev/null
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.tt
@@ -0,0 +1,5 @@
+<#
+ Session["ResourceFile"] = "CosmosStrings.resx";
+ Session["NoDiagnostics"] = true;
+#>
+<#@ include file="..\..\..\tools\Resources.tt" #>
\ No newline at end of file
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
new file mode 100644
index 00000000000..1b5c51ef014
--- /dev/null
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.
+
+
+ The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the key value '{keyValue}'.
+
+
+ The entity of type '{entityType}' cannot be queried directly because it is mapped as a part of the document mapped to '{principalEntityType}'. Rewrite the query to start with '{principalEntityType}'.
+
+
+ No matching discriminator values where found for this instance of '{entityType}'.
+
+
\ No newline at end of file
diff --git a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEagerLoadingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEagerLoadingExpressionVisitor.cs
deleted file mode 100644
index 4315b802564..00000000000
--- a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEagerLoadingExpressionVisitor.cs
+++ /dev/null
@@ -1,28 +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 Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal;
-
-namespace Microsoft.EntityFrameworkCore.Cosmos.Query.ExpressionVisitors.Internal
-{
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- public class CosmosEagerLoadingExpressionVisitor : EagerLoadingExpressionVisitor
- {
- public CosmosEagerLoadingExpressionVisitor(
- QueryCompilationContext queryCompilationContext,
- IQuerySourceTracingExpressionVisitorFactory querySourceTracingExpressionVisitorFactory)
- : base(queryCompilationContext, querySourceTracingExpressionVisitorFactory)
- {
- }
-
- public override bool ShouldInclude(INavigation navigation)
- => base.ShouldInclude(navigation)
- && navigation.GetTargetType().IsDocumentRoot();
- }
-}
diff --git a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEagerLoadingExpressionVisitorFactory.cs b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEagerLoadingExpressionVisitorFactory.cs
deleted file mode 100644
index c2b71d21c8c..00000000000
--- a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEagerLoadingExpressionVisitorFactory.cs
+++ /dev/null
@@ -1,24 +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 Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal;
-
-namespace Microsoft.EntityFrameworkCore.Cosmos.Query.ExpressionVisitors.Internal
-{
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- public class CosmosEagerLoadingExpressionVisitorFactory : IEagerLoadingExpressionVisitorFactory
- {
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- public EagerLoadingExpressionVisitor Create(
- QueryCompilationContext queryCompilationContext,
- IQuerySourceTracingExpressionVisitorFactory querySourceTracingExpressionVisitorFactory)
- => new CosmosEagerLoadingExpressionVisitor(queryCompilationContext, querySourceTracingExpressionVisitorFactory);
- }
-}
diff --git a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs
index 0604d29457a..a6630742087 100644
--- a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitor.cs
@@ -4,6 +4,8 @@
using System;
using System.Linq.Expressions;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Expressions.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -36,6 +38,11 @@ public CosmosEntityQueryableExpressionVisitor(
protected override Expression VisitEntityQueryable([NotNull] Type elementType)
{
var entityType = _model.FindEntityType(elementType);
+ if (!entityType.IsDocumentRoot())
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.QueryRootNestedEntityType(entityType.DisplayName(), entityType.FindOwnership().PrincipalEntityType.DisplayName()));
+ }
return new QueryShaperExpression(
QueryModelVisitor.QueryCompilationContext.IsAsyncQuery,
diff --git a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitorFactory.cs b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitorFactory.cs
index 84b9d74a435..527f3c7615e 100644
--- a/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitorFactory.cs
+++ b/src/EFCore.Cosmos/Query/ExpressionVisitors/Internal/CosmosEntityQueryableExpressionVisitorFactory.cs
@@ -3,7 +3,7 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
-using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query;
@@ -26,12 +26,10 @@ public CosmosEntityQueryableExpressionVisitorFactory(
}
public ExpressionVisitor Create(EntityQueryModelVisitor entityQueryModelVisitor, IQuerySource querySource)
- {
- return new CosmosEntityQueryableExpressionVisitor(
+ => new CosmosEntityQueryableExpressionVisitor(
_model,
_entityMaterializerSource,
(CosmosQueryModelVisitor)entityQueryModelVisitor,
querySource);
- }
}
}
diff --git a/src/EFCore.Cosmos/Query/Expressions/Internal/RootReferenceExpression.cs b/src/EFCore.Cosmos/Query/Expressions/Internal/RootReferenceExpression.cs
index 522f65a38b4..4f3f14bedbb 100644
--- a/src/EFCore.Cosmos/Query/Expressions/Internal/RootReferenceExpression.cs
+++ b/src/EFCore.Cosmos/Query/Expressions/Internal/RootReferenceExpression.cs
@@ -21,9 +21,6 @@ public RootReferenceExpression(IEntityType entityType, string alias)
_alias = alias;
}
- public override string ToString()
- {
- return _alias;
- }
+ public override string ToString() => _alias;
}
}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryModelVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryModelVisitor.cs
index 3a3d00f1268..86bfcda141c 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryModelVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryModelVisitor.cs
@@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Diagnostics;
using System.Linq.Expressions;
-using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Expressions.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Query.ExpressionVisitors.Internal;
using Microsoft.EntityFrameworkCore.Query;
diff --git a/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs b/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs
index e3dc2aa057a..50e12b8495e 100644
--- a/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs
+++ b/src/EFCore.Cosmos/Query/Internal/EntityShaper.cs
@@ -7,6 +7,7 @@
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -58,16 +59,13 @@ public virtual LambdaExpression CreateShaperLambda()
private NewExpression CreateEntityInfoExpression(IEntityType entityType, INavigation navigation)
{
- var valueBufferFactory = ValueBufferFactoryFactory.Create(entityType);
+ var usedProperties = new List();
+ var materializer = CreateMaterializerExpression(entityType, usedProperties, out var indexMap);
- var materializationContextParameter
- = Expression.Parameter(typeof(MaterializationContext), "materializationContext");
- var materializer = Expression.Lambda(_entityMaterializerSource
- .CreateMaterializeExpression(
- entityType, materializationContextParameter), materializationContextParameter);
+ var valueBufferFactory = ValueBufferFactoryFactory.Create(usedProperties);
var nestedEntities = new List();
- foreach (var ownedNavigation in entityType.GetNavigations())
+ foreach (var ownedNavigation in entityType.GetNavigations().Concat(entityType.GetDerivedNavigations()))
{
var fk = ownedNavigation.ForeignKey;
if (!fk.IsOwnership
@@ -88,12 +86,129 @@ var materializationContextParameter
return Expression.New(
EntityInfo.ConstructorInfo,
Expression.Constant(navigation, typeof(INavigation)),
- Expression.Constant(entityType.FindPrimaryKey()),
+ Expression.Constant(entityType.FindPrimaryKey(), typeof(IKey)),
valueBufferFactory,
materializer,
+ Expression.Constant(indexMap, typeof(Dictionary)),
nestedEntitiesExpression);
}
+ private LambdaExpression CreateMaterializerExpression(
+ IEntityType entityType,
+ List usedProperties,
+ out Dictionary typeIndexMap)
+ {
+ typeIndexMap = null;
+
+ var materializationContextParameter
+ = Expression.Parameter(typeof(MaterializationContext), "materializationContext");
+
+ var concreteEntityTypes = entityType.GetConcreteTypesInHierarchy().ToList();
+ var firstEntityType = concreteEntityTypes[0];
+ var indexMap = new int[firstEntityType.PropertyCount()];
+
+ foreach (var property in firstEntityType.GetProperties())
+ {
+ usedProperties.Add(property);
+ indexMap[property.GetIndex()] = usedProperties.Count - 1;
+ }
+
+ var materializer
+ = _entityMaterializerSource
+ .CreateMaterializeExpression(
+ firstEntityType, materializationContextParameter);
+
+ if (concreteEntityTypes.Count == 1)
+ {
+ return Expression.Lambda(materializer, materializationContextParameter);
+ }
+
+ var discriminatorProperty = firstEntityType.Cosmos().DiscriminatorProperty;
+
+ var firstDiscriminatorValue
+ = Expression.Constant(
+ firstEntityType.Cosmos().DiscriminatorValue,
+ discriminatorProperty.ClrType);
+
+ var discriminatorValueVariable
+ = Expression.Variable(discriminatorProperty.ClrType);
+
+ var returnLabelTarget = Expression.Label(entityType.ClrType);
+
+ var blockExpressions
+ = new Expression[]
+ {
+ Expression.Assign(
+ discriminatorValueVariable,
+ _entityMaterializerSource
+ .CreateReadValueExpression(
+ Expression.Call(materializationContextParameter, MaterializationContext.GetValueBufferMethod),
+ discriminatorProperty.ClrType,
+ indexMap[discriminatorProperty.GetIndex()],
+ discriminatorProperty)),
+ Expression.IfThenElse(
+ Expression.Equal(discriminatorValueVariable, firstDiscriminatorValue),
+ Expression.Return(returnLabelTarget, materializer),
+ Expression.Throw(
+ Expression.Call(
+ _createUnableToDiscriminateException,
+ Expression.Constant(firstEntityType)))),
+ Expression.Label(
+ returnLabelTarget,
+ Expression.Default(returnLabelTarget.Type))
+ };
+
+ foreach (var concreteEntityType in concreteEntityTypes.Skip(1))
+ {
+ indexMap = new int[concreteEntityType.PropertyCount()];
+
+ var shadowPropertyExists = false;
+
+ foreach (var property in concreteEntityType.GetProperties())
+ {
+ var propertyIndex = usedProperties.IndexOf(property);
+ if (propertyIndex == -1)
+ {
+ usedProperties.Add(property);
+ propertyIndex = usedProperties.Count - 1;
+ }
+ indexMap[property.GetIndex()] = propertyIndex;
+
+ shadowPropertyExists = shadowPropertyExists || property.IsShadowProperty;
+ }
+
+ if (shadowPropertyExists)
+ {
+ if (typeIndexMap == null)
+ {
+ typeIndexMap = new Dictionary();
+ }
+
+ typeIndexMap[concreteEntityType.ClrType] = indexMap;
+ }
+
+ var discriminatorValue
+ = Expression.Constant(
+ concreteEntityType.Cosmos().DiscriminatorValue,
+ discriminatorProperty.ClrType);
+
+ materializer
+ = _entityMaterializerSource
+ .CreateMaterializeExpression(
+ concreteEntityType, materializationContextParameter);
+
+ blockExpressions[1]
+ = Expression.IfThenElse(
+ Expression.Equal(discriminatorValueVariable, discriminatorValue),
+ Expression.Return(returnLabelTarget, materializer),
+ blockExpressions[1]);
+ }
+
+ return Expression.Lambda(
+ Expression.Block(new[] { discriminatorValueVariable }, blockExpressions),
+ materializationContextParameter);
+ }
+
private static readonly MethodInfo _listAddMethodInfo
= typeof(List).GetTypeInfo().GetDeclaredMethod(nameof(List.Add));
@@ -143,7 +258,8 @@ private static object Shape(
entityInfo.Key,
new EntityLoadInfo(
new MaterializationContext(valueBuffer, queryContext.Context),
- entityInfo.Materializer),
+ entityInfo.Materializer,
+ entityInfo.TypeIndexMap),
queryStateManager: trackingQuery,
throwOnNullKey: true);
@@ -214,6 +330,14 @@ private static object ShapeNestedEntities(
return parentEntity;
}
+ private static readonly MethodInfo _createUnableToDiscriminateException
+ = typeof(EntityShaper).GetTypeInfo()
+ .GetDeclaredMethod(nameof(CreateUnableToDiscriminateException));
+
+ [UsedImplicitly]
+ private static Exception CreateUnableToDiscriminateException(IEntityType entityType)
+ => new InvalidOperationException(CosmosStrings.UnableToDiscriminate(entityType.DisplayName()));
+
private class EntityInfo
{
public static readonly ConstructorInfo ConstructorInfo
@@ -224,12 +348,14 @@ public EntityInfo(
IKey key,
Func valueBufferFactory,
Func materializer,
+ Dictionary typeIndexMap,
IList nestedEntities)
{
Navigation = navigation;
Key = key;
ValueBufferFactory = valueBufferFactory;
Materializer = materializer;
+ TypeIndexMap = typeIndexMap;
NestedEntities = nestedEntities;
}
@@ -237,6 +363,7 @@ public EntityInfo(
public IKey Key { get; }
public Func ValueBufferFactory { get; }
public Func Materializer { get; }
+ public Dictionary TypeIndexMap { get; }
public IList NestedEntities { get; }
}
}
diff --git a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs b/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs
index b08483678cd..9a2f27d4908 100644
--- a/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/ValueBufferFactoryFactory.cs
@@ -2,6 +2,7 @@
// 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;
@@ -20,11 +21,11 @@ private static readonly MethodInfo _getItemMethodInfo
.Single(pi => pi.Name == "Item" && pi.GetIndexParameters()[0].ParameterType == typeof(string))
.GetMethod;
- public static Expression> Create(IEntityType entityType)
+ public static Expression> Create(List usedProperties)
=> Expression.Lambda>(
Expression.NewArrayInit(
typeof(object),
- entityType.GetProperties()
+ usedProperties
.Select(p =>
CreateGetValueExpression(
_jObjectParameter,
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosClient.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosClient.cs
index 79cb0adc5c9..5759cb34422 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosClient.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosClient.cs
@@ -258,7 +258,7 @@ public bool CreateDocumentOnce(
var response = (HttpWebResponse)request.GetResponse();
- return response.StatusCode == HttpStatusCode.OK;
+ return response.StatusCode == HttpStatusCode.Created;
}
public Task CreateDocumentAsync(
@@ -287,7 +287,7 @@ public async Task CreateDocumentOnceAsync(
throw new HttpException(response);
}
- return response.StatusCode == HttpStatusCode.OK;
+ return response.StatusCode == HttpStatusCode.Created;
}
public bool ReplaceDocument(
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabase.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabase.cs
index 165b901ed48..89eca11f998 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabase.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabase.cs
@@ -1,18 +1,22 @@
// 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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Update.Internal;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;
+using Microsoft.EntityFrameworkCore.Update.Internal;
using Newtonsoft.Json.Linq;
namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal
@@ -22,13 +26,20 @@ public class CosmosDatabase : Database
private readonly Dictionary _documentCollections
= new Dictionary();
private readonly CosmosClient _cosmosClient;
+ private readonly bool _sensitiveLoggingEnabled;
public CosmosDatabase(
DatabaseDependencies dependencies,
- CosmosClient cosmosClient)
+ CosmosClient cosmosClient,
+ ILoggingOptions loggingOptions)
: base(dependencies)
{
_cosmosClient = cosmosClient;
+
+ if (loggingOptions.IsSensitiveDataLoggingEnabled)
+ {
+ _sensitiveLoggingEnabled = true;
+ }
}
public override int SaveChanges(IReadOnlyList entries)
@@ -48,9 +59,11 @@ public override int SaveChanges(IReadOnlyList entries)
if (!entityType.IsDocumentRoot())
{
var root = GetRootDocument((InternalEntityEntry)entry);
- if (!entriesSaved.Contains(root))
+ if (!entriesSaved.Contains(root)
+ && rootEntriesToSave.Add(root)
+ && root.EntityState == EntityState.Unchanged)
{
- rootEntriesToSave.Add(root);
+ ((InternalEntityEntry)root).SetEntityState(EntityState.Modified);
}
continue;
}
@@ -79,14 +92,30 @@ private bool Save(IUpdateEntry entry)
var entityType = entry.EntityType;
var documentSource = GetDocumentSource(entityType);
var collectionId = documentSource.GetCollectionId();
+ var state = entry.EntityState;
- switch (entry.EntityState)
+ if (entry.SharedIdentityEntry != null)
+ {
+ if (entry.EntityState == EntityState.Deleted)
+ {
+ return false;
+ }
+
+ if (state == EntityState.Added)
+ {
+ state = EntityState.Modified;
+ }
+ }
+
+ switch (state)
{
case EntityState.Added:
return _cosmosClient.CreateDocument(collectionId, documentSource.CreateDocument(entry));
case EntityState.Modified:
var jObjectProperty = entityType.FindProperty(StoreKeyConvention.JObjectPropertyName);
- var document = jObjectProperty != null ? (JObject)entry.GetCurrentValue(jObjectProperty) : null;
+ var document = jObjectProperty != null
+ ? (JObject)(entry.SharedIdentityEntry ?? entry).GetCurrentValue(jObjectProperty)
+ : null;
if (document != null)
{
documentSource.UpdateDocument(document, entry);
@@ -94,13 +123,10 @@ private bool Save(IUpdateEntry entry)
else
{
document = documentSource.CreateDocument(entry);
-
- // Set Discriminator Property for updates
- document[entityType.Cosmos().DiscriminatorProperty.Name] =
- JToken.FromObject(entityType.Cosmos().DiscriminatorValue);
}
- return _cosmosClient.ReplaceDocument(collectionId, documentSource.GetId(entry), document);
+ return _cosmosClient.ReplaceDocument(
+ collectionId, documentSource.GetId(entry.SharedIdentityEntry ?? entry), document);
case EntityState.Deleted:
return _cosmosClient.DeleteDocument(collectionId, documentSource.GetId(entry));
default:
@@ -156,14 +182,30 @@ private Task SaveAsync(IUpdateEntry entry, CancellationToken cancellationT
var entityType = entry.EntityType;
var documentSource = GetDocumentSource(entityType);
var collectionId = documentSource.GetCollectionId();
+ var state = entry.EntityState;
+
+ if (entry.SharedIdentityEntry != null)
+ {
+ if (entry.EntityState == EntityState.Deleted)
+ {
+ return Task.FromResult(false);
+ }
+
+ if (state == EntityState.Added)
+ {
+ state = EntityState.Modified;
+ }
+ }
- switch (entry.EntityState)
+ switch (state)
{
case EntityState.Added:
return _cosmosClient.CreateDocumentAsync(collectionId, documentSource.CreateDocument(entry), cancellationToken);
case EntityState.Modified:
var jObjectProperty = entityType.FindProperty(StoreKeyConvention.JObjectPropertyName);
- var document = jObjectProperty != null ? (JObject)entry.GetCurrentValue(jObjectProperty) : null;
+ var document = jObjectProperty != null
+ ? (JObject)(entry.SharedIdentityEntry ?? entry).GetCurrentValue(jObjectProperty)
+ : null;
if (document != null)
{
documentSource.UpdateDocument(document, entry);
@@ -177,7 +219,8 @@ private Task SaveAsync(IUpdateEntry entry, CancellationToken cancellationT
JToken.FromObject(entityType.Cosmos().DiscriminatorValue);
}
- return _cosmosClient.ReplaceDocumentAsync(collectionId, documentSource.GetId(entry), document, cancellationToken);
+ return _cosmosClient.ReplaceDocumentAsync(
+ collectionId, documentSource.GetId(entry.SharedIdentityEntry ?? entry), document, cancellationToken);
case EntityState.Deleted:
return _cosmosClient.DeleteDocumentAsync(collectionId, documentSource.GetId(entry), cancellationToken);
default:
@@ -199,7 +242,25 @@ public DocumentSource GetDocumentSource(IEntityType entityType)
private IUpdateEntry GetRootDocument(InternalEntityEntry entry)
{
var stateManager = entry.StateManager;
- var principal = stateManager.GetPrincipal(entry, entry.EntityType.FindOwnership());
+ var ownership = entry.EntityType.FindOwnership();
+ var principal = stateManager.GetPrincipal(entry, ownership);
+ if (principal == null)
+ {
+ if (_sensitiveLoggingEnabled)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.OrphanedNestedDocumentSensitive(
+ entry.EntityType.DisplayName(),
+ ownership.PrincipalEntityType.DisplayName(),
+ entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties)));
+ }
+
+ throw new InvalidOperationException(
+ CosmosStrings.OrphanedNestedDocument(
+ entry.EntityType.DisplayName(),
+ ownership.PrincipalEntityType.DisplayName()));
+ }
+
return principal.EntityType.IsDocumentRoot() ? principal : GetRootDocument(principal);
}
}
diff --git a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
index 2c70317721a..2852a0f8108 100644
--- a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
+++ b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs
@@ -15,26 +15,26 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Update.Internal
public class DocumentSource
{
private readonly string _collectionId;
- private readonly IEntityType _entityType;
private readonly CosmosDatabase _database;
+ private readonly IProperty _idProperty;
public DocumentSource(IEntityType entityType, CosmosDatabase database)
{
_collectionId = entityType.Cosmos().ContainerName;
- _entityType = entityType;
_database = database;
+ _idProperty = entityType.FindProperty(StoreKeyConvention.IdPropertyName);
}
public string GetCollectionId()
=> _collectionId;
public string GetId(IUpdateEntry entry)
- => entry.GetCurrentValue(_entityType.FindProperty(StoreKeyConvention.IdPropertyName));
+ => entry.GetCurrentValue(_idProperty);
public JObject CreateDocument(IUpdateEntry entry)
{
var document = new JObject();
- foreach (var property in _entityType.GetProperties())
+ foreach (var property in entry.EntityType.GetProperties())
{
if (property.Name != StoreKeyConvention.JObjectPropertyName)
{
@@ -43,7 +43,7 @@ public JObject CreateDocument(IUpdateEntry entry)
}
}
- foreach (var ownedNavigation in _entityType.GetNavigations())
+ foreach (var ownedNavigation in entry.EntityType.GetNavigations())
{
var fk = ownedNavigation.ForeignKey;
if (!fk.IsOwnership
@@ -58,8 +58,7 @@ public JObject CreateDocument(IUpdateEntry entry)
{
document[ownedNavigation.Name] = null;
}
-
- if (fk.IsUnique)
+ else if (fk.IsUnique)
{
var dependentEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(nestedValue, fk.DeclaringEntityType);
document[ownedNavigation.Name] = _database.GetDocumentSource(dependentEntry.EntityType).CreateDocument(dependentEntry);
@@ -82,17 +81,19 @@ public JObject CreateDocument(IUpdateEntry entry)
public JObject UpdateDocument(JObject document, IUpdateEntry entry)
{
- foreach (var property in _entityType.GetProperties())
+ foreach (var property in entry.EntityType.GetProperties())
{
if (property.Name != StoreKeyConvention.JObjectPropertyName
- && entry.IsModified(property))
+ && property.Name != StoreKeyConvention.IdPropertyName
+ && (entry.EntityState == EntityState.Added
+ || entry.IsModified(property)))
{
var value = entry.GetCurrentValue(property);
document[property.Name] = value != null ? JToken.FromObject(value) : null;
}
}
- foreach (var ownedNavigation in _entityType.GetNavigations())
+ foreach (var ownedNavigation in entry.EntityType.GetNavigations())
{
var fk = ownedNavigation.ForeignKey;
if (!fk.IsOwnership
@@ -108,8 +109,7 @@ public JObject UpdateDocument(JObject document, IUpdateEntry entry)
{
document[ownedNavigation.Name] = null;
}
-
- if (fk.IsUnique)
+ else if (fk.IsUnique)
{
var nestedEntry = ((InternalEntityEntry)entry).StateManager.TryGetEntry(nestedValue, fk.DeclaringEntityType);
var nestedDocument = (JObject)document[ownedNavigation.Name];
diff --git a/src/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/src/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs
index c037b95a59d..2d46506b002 100644
--- a/src/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs
+++ b/src/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs
@@ -19,7 +19,7 @@ public abstract class TableSplittingTestBase
{
protected TableSplittingTestBase(ITestOutputHelper testOutputHelper)
{
- TestSqlLoggerFactory = (TestSqlLoggerFactory)TestStoreFactory.CreateListLoggerFactory(l => true);
+ TestSqlLoggerFactory = (TestSqlLoggerFactory)TestStoreFactory.CreateListLoggerFactory(_ => true);
//TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/CompositeShaper.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/CompositeShaper.cs
index 415f3c0eff7..aab2fafc24e 100644
--- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/CompositeShaper.cs
+++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/CompositeShaper.cs
@@ -33,8 +33,7 @@ public static Shaper Create(
Check.NotNull(innerShaper, nameof(innerShaper));
Check.NotNull(materializer, nameof(materializer));
- var compositeShaper
- = (Shaper)_createCompositeShaperMethodInfo
+ return (Shaper)_createCompositeShaperMethodInfo
.MakeGenericMethod(
outerShaper.GetType(),
outerShaper.Type,
@@ -51,8 +50,6 @@ var compositeShaper
materializer.Compile(),
storeMaterializerExpression ? materializer : null
});
-
- return compositeShaper;
}
///
@@ -175,11 +172,9 @@ public override Shaper AddOffset(int offset)
}
public override Shaper Unwrap(IQuerySource querySource)
- {
- return _outerShaper.Unwrap(querySource)
+ => _outerShaper.Unwrap(querySource)
?? _innerShaper.Unwrap(querySource)
?? base.Unwrap(querySource);
- }
}
}
}
diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs
index d2df430410f..d52815004d3 100644
--- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs
+++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs
@@ -24,9 +24,6 @@ public class MaterializerFactory : IMaterializerFactory
{
private readonly IEntityMaterializerSource _entityMaterializerSource;
- private static readonly MethodInfo _getValueBufferMethod
- = typeof(MaterializationContext).GetProperty(nameof(MaterializationContext.ValueBuffer)).GetMethod;
-
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@@ -96,7 +93,7 @@ var blockExpressions
discriminatorValueVariable,
_entityMaterializerSource
.CreateReadValueExpression(
- Expression.Call(materializationContextParameter, _getValueBufferMethod),
+ Expression.Call(materializationContextParameter, MaterializationContext.GetValueBufferMethod),
discriminatorProperty.ClrType,
indexMap[discriminatorProperty.GetIndex()],
discriminatorProperty)),
diff --git a/src/EFCore.Specification.Tests/TestModels/TransportationModel/FuelTank.cs b/src/EFCore.Specification.Tests/TestModels/TransportationModel/FuelTank.cs
index d53e9fc8a78..883da084be8 100644
--- a/src/EFCore.Specification.Tests/TestModels/TransportationModel/FuelTank.cs
+++ b/src/EFCore.Specification.Tests/TestModels/TransportationModel/FuelTank.cs
@@ -10,8 +10,7 @@ public class FuelTank
public string Capacity { get; set; }
- // #9005
- //public PoweredVehicle Vehicle { get; set; }
+ public PoweredVehicle Vehicle { get; set; }
public CombustionEngine Engine { get; set; }
public override bool Equals(object obj)
diff --git a/src/EFCore.Specification.Tests/TestModels/TransportationModel/TransportationContext.cs b/src/EFCore.Specification.Tests/TestModels/TransportationModel/TransportationContext.cs
index cd36b07cafa..a286d23257b 100644
--- a/src/EFCore.Specification.Tests/TestModels/TransportationModel/TransportationContext.cs
+++ b/src/EFCore.Specification.Tests/TestModels/TransportationModel/TransportationContext.cs
@@ -58,6 +58,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
//eb.HasOne(e => e.Vehicle)
// .WithOne()
// .HasForeignKey(e => e.VehicleName);
+ eb.Ignore(e => e.Vehicle);
});
modelBuilder.Entity(
@@ -76,13 +77,15 @@ public void Seed()
}
public void AssertSeeded()
- => Assert.Equal(
- CreateVehicles().OrderBy(v => v.Name).ToList(),
- Vehicles
- .Include(v => v.Operator)
- .Include(v => ((PoweredVehicle)v).Engine)
- .ThenInclude(e => (e as CombustionEngine).FuelTank)
- .OrderBy(v => v.Name).ToList());
+ {
+ var expected = CreateVehicles().OrderBy(v => v.Name).ToList();
+ var actual = Vehicles
+ .Include(v => v.Operator)
+ .Include(v => ((PoweredVehicle)v).Engine)
+ .ThenInclude(e => (e as CombustionEngine).FuelTank)
+ .OrderBy(v => v.Name).ToList();
+ Assert.Equal(expected, actual);
+ }
protected IEnumerable CreateVehicles()
=> new List
diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs
index edbcf400207..ed350572724 100644
--- a/src/EFCore/Infrastructure/ModelValidator.cs
+++ b/src/EFCore/Infrastructure/ModelValidator.cs
@@ -303,7 +303,8 @@ protected virtual void ValidateOwnership([NotNull] IModel model)
throw new InvalidOperationException(CoreStrings.OwnedDerivedType(entityType.DisplayName()));
}
- foreach (var referencingFk in entityType.GetReferencingForeignKeys().Where(fk => !fk.IsOwnership))
+ foreach (var referencingFk in entityType.GetReferencingForeignKeys().Where(fk => !fk.IsOwnership
+ && !Contains(fk.DeclaringEntityType.FindOwnership(), fk)))
{
throw new InvalidOperationException(
CoreStrings.PrincipalOwnedType(
@@ -318,7 +319,8 @@ protected virtual void ValidateOwnership([NotNull] IModel model)
entityType.DisplayName()));
}
- foreach (var fk in entityType.GetDeclaredForeignKeys().Where(fk => !fk.IsOwnership && fk.PrincipalToDependent != null))
+ foreach (var fk in entityType.GetDeclaredForeignKeys().Where(fk => !fk.IsOwnership && fk.PrincipalToDependent != null
+ && !Contains(fk.DeclaringEntityType.FindOwnership(), fk)))
{
throw new InvalidOperationException(
CoreStrings.InverseToOwnedType(
@@ -335,6 +337,11 @@ protected virtual void ValidateOwnership([NotNull] IModel model)
}
}
+ private bool Contains(IForeignKey inheritedFk, IForeignKey derivedFk)
+ => inheritedFk != null
+ && inheritedFk.PrincipalEntityType.IsAssignableFrom(derivedFk.PrincipalEntityType)
+ && PropertyListComparer.Instance.Equals(inheritedFk.Properties, derivedFk.Properties);
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs
index 08ebc857b68..eeaa2795698 100644
--- a/src/EFCore/Metadata/Internal/EntityType.cs
+++ b/src/EFCore/Metadata/Internal/EntityType.cs
@@ -2124,10 +2124,29 @@ public virtual void AddData([NotNull] IEnumerable