Skip to content

Commit

Permalink
Add model building support for collections of owned types.
Browse files Browse the repository at this point in the history
Add HasKey() to ReferenceOwnershipBuilder
Add missing string overloads of OwnsOne() and HasOne()
Add hiding overloads of HasData()
Sort methods and fix some comments
Move owned type primary key configuration to KeyDiscoveryConvention

Part of #8172
  • Loading branch information
AndriySvyryd committed Jun 29, 2018
1 parent e91014d commit f54e449
Show file tree
Hide file tree
Showing 45 changed files with 4,421 additions and 856 deletions.
1 change: 1 addition & 0 deletions EFCore.Runtime.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Microsoft_002EEntityFrameworkCore_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=D44E9AA0167CE64B866A64486B319AB0/RelativePath/@EntryValue">..\EFCore.sln.DotSettings</s:String>
<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=D44E9AA0167CE64B866A64486B319AB0/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileD44E9AA0167CE64B866A64486B319AB0/@KeyIndexDefined">True</s:Boolean>
Expand Down
10 changes: 4 additions & 6 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,14 @@ protected virtual void GenerateEntityType(
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(stringBuilder, nameof(stringBuilder));

var ownerNavigation = entityType.FindOwnership()?.PrincipalToDependent.Name;
var ownership = entityType.FindOwnership();
var ownerNavigation = ownership?.PrincipalToDependent.Name;

stringBuilder
.Append(builderName)
.Append(
ownerNavigation != null
? ".OwnsOne("
? ownership.IsUnique ? ".OwnsOne(" : ".OwnsMany("
: ".Entity(")
.Append(Code.Literal(entityType.Name));

Expand Down Expand Up @@ -186,10 +187,7 @@ protected virtual void GenerateEntityType(

GenerateProperties(builderName, entityType.GetDeclaredProperties(), stringBuilder);

if (ownerNavigation == null)
{
GenerateKeys(builderName, entityType.GetDeclaredKeys(), entityType.FindDeclaredPrimaryKey(), stringBuilder);
}
GenerateKeys(builderName, entityType.GetDeclaredKeys(), entityType.FindDeclaredPrimaryKey(), stringBuilder);

GenerateIndexes(builderName, entityType.GetDeclaredIndexes(), stringBuilder);

Expand Down
65 changes: 46 additions & 19 deletions src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -43,41 +44,67 @@ public SnapshotModelProcessor([NotNull] IOperationReporter operationReporter)
/// </summary>
public virtual IModel Process(IModel model)
{
if (model != null
&& model.GetProductVersion()?.StartsWith("1.") == true)
if (model == null)
{
ProcessElement(model);
return null;
}

foreach (var entityType in model.GetEntityTypes())
{
ProcessElement(entityType);
ProcessCollection(entityType.GetProperties());
ProcessCollection(entityType.GetKeys());
ProcessCollection(entityType.GetIndexes());
var version = model.GetProductVersion();
if (version == null)
{
return model;
}

foreach (var element in entityType.GetForeignKeys())
{
ProcessElement(element);
ProcessElement(element.DependentToPrincipal);
ProcessElement(element.PrincipalToDependent);
}
ProcessElement(model, version);

foreach (var entityType in model.GetEntityTypes())
{
ProcessElement(entityType, version);
ProcessCollection(entityType.GetProperties(), version);
ProcessCollection(entityType.GetKeys(), version);
ProcessCollection(entityType.GetIndexes(), version);

foreach (var element in entityType.GetForeignKeys())
{
ProcessElement(element, version);
ProcessElement(element.DependentToPrincipal, version);
ProcessElement(element.PrincipalToDependent, version);
}
}

return model;
}

private void ProcessCollection(IEnumerable<IAnnotatable> metadata)
private void ProcessCollection(IEnumerable<IAnnotatable> metadata, string version)
{
foreach (var element in metadata)
{
ProcessElement(element);
ProcessElement(element, version);
}
}

private void ProcessElement(IEntityType entityType, string version)
{
ProcessElement((IAnnotatable)entityType, version);

if ((version.StartsWith("2.0", StringComparison.Ordinal)
|| version.StartsWith("2.1", StringComparison.Ordinal))
&& entityType is IMutableEntityType mutableEntityType
&& entityType.FindPrimaryKey() == null)
{
var ownership = mutableEntityType.FindOwnership();
if (ownership is IMutableForeignKey mutableOwnership
&& ownership.IsUnique)
{
mutableEntityType.SetPrimaryKey(mutableOwnership.Properties);
}
}
}

private void ProcessElement(IAnnotatable metadata)
private void ProcessElement(IAnnotatable metadata, string version)
{
if (metadata is IMutableAnnotatable mutableMetadata)
if (version.StartsWith("1.", StringComparison.Ordinal)
&& metadata is IMutableAnnotatable mutableMetadata)
{
foreach (var annotation in mutableMetadata.GetAnnotations().ToList())
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Relational database specific extension methods for <see cref="CollectionOwnershipBuilder" />.
/// </summary>
public static class RelationalCollectionOwnershipBuilderExtensions
{
/// <summary>
/// Configures the view or table that the entity maps to when targeting a relational database.
/// </summary>
/// <param name="collectionOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the view or table. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static CollectionOwnershipBuilder ToTable(
[NotNull] this CollectionOwnershipBuilder collectionOwnershipBuilder,
[CanBeNull] string name)
{
Check.NotNull(collectionOwnershipBuilder, nameof(collectionOwnershipBuilder));
Check.NullButNotEmpty(name, nameof(name));

collectionOwnershipBuilder.GetInfrastructure<InternalEntityTypeBuilder>()
.Relational(ConfigurationSource.Explicit)
.ToTable(name);

return collectionOwnershipBuilder;
}

/// <summary>
/// Configures the view or table that the entity maps to when targeting a relational database.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <typeparam name="TDependentEntity"> The entity type that this relationship targets. </typeparam>
/// <param name="collectionOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the view or table. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static CollectionOwnershipBuilder<TEntity, TDependentEntity> ToTable<TEntity, TDependentEntity>(
[NotNull] this CollectionOwnershipBuilder<TEntity, TDependentEntity> collectionOwnershipBuilder,
[CanBeNull] string name)
where TEntity : class
where TDependentEntity : class
=> (CollectionOwnershipBuilder<TEntity, TDependentEntity>)ToTable((CollectionOwnershipBuilder)collectionOwnershipBuilder, name);

/// <summary>
/// Configures the view or table that the entity maps to when targeting a relational database.
/// </summary>
/// <param name="collectionOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the view or table. </param>
/// <param name="schema"> The schema of the view or table. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static CollectionOwnershipBuilder ToTable(
[NotNull] this CollectionOwnershipBuilder collectionOwnershipBuilder,
[CanBeNull] string name,
[CanBeNull] string schema)
{
Check.NotNull(collectionOwnershipBuilder, nameof(collectionOwnershipBuilder));
Check.NullButNotEmpty(name, nameof(name));
Check.NullButNotEmpty(schema, nameof(schema));

collectionOwnershipBuilder.GetInfrastructure<InternalEntityTypeBuilder>()
.Relational(ConfigurationSource.Explicit)
.ToTable(name, schema);

return collectionOwnershipBuilder;
}

/// <summary>
/// Configures the view or table that the entity maps to when targeting a relational database.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <typeparam name="TDependentEntity"> The entity type that this relationship targets. </typeparam>
/// <param name="collectionOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the view or table. </param>
/// <param name="schema"> The schema of the view or table. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static CollectionOwnershipBuilder<TEntity, TDependentEntity> ToTable<TEntity, TDependentEntity>(
[NotNull] this CollectionOwnershipBuilder<TEntity, TDependentEntity> collectionOwnershipBuilder,
[CanBeNull] string name,
[CanBeNull] string schema)
where TEntity : class
where TDependentEntity : class
=> (CollectionOwnershipBuilder<TEntity, TDependentEntity>)ToTable((CollectionOwnershipBuilder)collectionOwnershipBuilder, name, schema);

/// <summary>
/// Configures the foreign key constraint name for this relationship when targeting a relational database.
/// </summary>
/// <param name="referenceReferenceBuilder"> The builder being used to configure the relationship. </param>
/// <param name="name"> The name of the foreign key constraint. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static CollectionOwnershipBuilder HasConstraintName(
[NotNull] this CollectionOwnershipBuilder referenceReferenceBuilder,
[CanBeNull] string name)
{
Check.NotNull(referenceReferenceBuilder, nameof(referenceReferenceBuilder));
Check.NullButNotEmpty(name, nameof(name));

referenceReferenceBuilder.GetInfrastructure<InternalRelationshipBuilder>()
.Relational(ConfigurationSource.Explicit)
.HasConstraintName(name);

return referenceReferenceBuilder;
}

/// <summary>
/// Configures the foreign key constraint name for this relationship when targeting a relational database.
/// </summary>
/// <param name="referenceReferenceBuilder"> The builder being used to configure the relationship. </param>
/// <param name="name"> The name of the foreign key constraint. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
/// <typeparam name="TEntity"> The entity type on one end of the relationship. </typeparam>
/// <typeparam name="TDependentEntity"> The entity type on the other end of the relationship. </typeparam>
public static CollectionOwnershipBuilder<TEntity, TDependentEntity> HasConstraintName<TEntity, TDependentEntity>(
[NotNull] this CollectionOwnershipBuilder<TEntity, TDependentEntity> referenceReferenceBuilder,
[CanBeNull] string name)
where TEntity : class
where TDependentEntity : class
=> (CollectionOwnershipBuilder<TEntity, TDependentEntity>)HasConstraintName(
(CollectionOwnershipBuilder)referenceReferenceBuilder, name);
}
}
4 changes: 2 additions & 2 deletions src/EFCore.Relational/Storage/Internal/RelationalCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ protected virtual DbCommand CreateCommand(
command.CommandText = AdjustCommandText(CommandText);

ConfigureCommand(command);

if (connection.CurrentTransaction != null)
{
command.Transaction = connection.CurrentTransaction.GetDbTransaction();
Expand Down Expand Up @@ -388,7 +388,7 @@ protected virtual DbCommand CreateCommand(
protected virtual void ConfigureCommand(DbCommand command)
{
}

/// <summary>
/// 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.Builders;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// SQL Server specific extension methods for <see cref="CollectionOwnershipBuilder" />.
/// </summary>
public static class SqlServerCollectionOwnershipBuilderExtensions
{
/// <summary>
/// Configures the table that the entity maps to when targeting SQL Server as memory-optimized.
/// </summary>
/// <param name="collectionOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="memoryOptimized"> A value indicating whether the table is memory-optimized. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static CollectionOwnershipBuilder ForSqlServerIsMemoryOptimized(
[NotNull] this CollectionOwnershipBuilder collectionOwnershipBuilder, bool memoryOptimized = true)
{
Check.NotNull(collectionOwnershipBuilder, nameof(collectionOwnershipBuilder));

collectionOwnershipBuilder.OwnedEntityType.SqlServer().IsMemoryOptimized = memoryOptimized;

return collectionOwnershipBuilder;
}

/// <summary>
/// Configures the table that the entity maps to when targeting SQL Server as memory-optimized.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <typeparam name="TRelatedEntity"> The entity type that this relationship targets. </typeparam>
/// <param name="collectionOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="memoryOptimized"> A value indicating whether the table is memory-optimized. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static CollectionOwnershipBuilder<TEntity, TRelatedEntity> ForSqlServerIsMemoryOptimized<TEntity, TRelatedEntity>(
[NotNull] this CollectionOwnershipBuilder<TEntity, TRelatedEntity> collectionOwnershipBuilder, bool memoryOptimized = true)
where TEntity : class
where TRelatedEntity : class
=> (CollectionOwnershipBuilder<TEntity, TRelatedEntity>)ForSqlServerIsMemoryOptimized((CollectionOwnershipBuilder)collectionOwnershipBuilder, memoryOptimized);
}
}
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,13 @@ private InternalRelationshipBuilder WithOneBuilder(PropertyIdentity reference)
}

return referenceName != null
&& RelatedEntityType != foreignKey.DeclaringEntityType
&& RelatedEntityType != foreignKey.DeclaringEntityType
? reference.Property == null && CollectionProperty == null
? Builder.Navigations(reference.Name, CollectionName, DeclaringEntityType, RelatedEntityType, ConfigurationSource.Explicit)
: Builder.Navigations(reference.Property, CollectionProperty, DeclaringEntityType, RelatedEntityType, ConfigurationSource.Explicit)
: reference.Property == null
? Builder.DependentToPrincipal(reference.Name, ConfigurationSource.Explicit)
: Builder.DependentToPrincipal(reference.Property, ConfigurationSource.Explicit);
? Builder.DependentToPrincipal(reference.Name, ConfigurationSource.Explicit)
: Builder.DependentToPrincipal(reference.Property, ConfigurationSource.Explicit);
}

#region Hidden System.Object members
Expand Down
Loading

0 comments on commit f54e449

Please sign in to comment.