diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
index 614e687e199..6b68afac485 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
@@ -96,6 +96,7 @@ private enum Id
DuplicateColumnOrders,
ForeignKeyTpcPrincipalWarning,
TpcStoreGeneratedIdentityWarning,
+ KeyUnmappedProperties,
// Update events
BatchReadyForExecution = CoreEventId.RelationalBaseId + 700,
@@ -799,6 +800,20 @@ private static EventId MakeValidationId(Id id)
public static readonly EventId IndexPropertiesMappedToNonOverlappingTables =
MakeValidationId(Id.IndexPropertiesMappedToNonOverlappingTables);
+ ///
+ /// A key specifies properties which don't map to a single table.
+ ///
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ ///
+ /// This event uses the payload when used with a .
+ ///
+ ///
+ public static readonly EventId KeyUnmappedProperties =
+ MakeValidationId(Id.KeyUnmappedProperties);
+
///
/// A foreign key specifies properties which don't map to the related tables.
///
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
index 82492b22aa6..93bf91d2cb6 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
@@ -2774,6 +2774,47 @@ private static string NamedIndexPropertiesMappedToNonOverlappingTables(EventDefi
p.TablesMappedToProperty2.FormatTables()));
}
+ ///
+ /// Logs the event.
+ ///
+ /// The diagnostics logger to use.
+ /// The foreign key.
+ public static void KeyUnmappedProperties(
+ this IDiagnosticsLogger diagnostics,
+ IKey key)
+ {
+ var definition = RelationalResources.LogKeyUnmappedProperties(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(
+ diagnostics,
+ key.Properties.Format(),
+ key.DeclaringEntityType.DisplayName(),
+ key.DeclaringEntityType.GetSchemaQualifiedTableName()!);
+ }
+
+ if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ var eventData = new KeyEventData(
+ definition,
+ KeyUnmappedProperties,
+ key);
+
+ diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
+ }
+ }
+
+ private static string KeyUnmappedProperties(EventDefinitionBase definition, EventData payload)
+ {
+ var d = (EventDefinition)definition;
+ var p = (KeyEventData)payload;
+ return d.GenerateMessage(
+ p.Key.Properties.Format(),
+ p.Key.DeclaringEntityType.DisplayName(),
+ p.Key.DeclaringEntityType.GetSchemaQualifiedTableName()!);
+ }
+
///
/// Logs the event.
///
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
index d77e753cadf..ba7c869f997 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
@@ -520,6 +520,15 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[EntityFrameworkInternal]
public EventDefinitionBase? LogUnnamedIndexPropertiesMappedToNonOverlappingTables;
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public EventDefinitionBase? LogKeyUnmappedProperties;
+
///
/// 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.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
index 40837779424..bef4f64ea75 100644
--- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
@@ -842,6 +842,36 @@ public static void SetComment(this IMutableEntityType entityType, string? commen
public static IEnumerable GetMappingFragments(this IReadOnlyEntityType entityType)
=> EntityTypeMappingFragment.Get(entityType) ?? Enumerable.Empty();
+ ///
+ ///
+ /// Returns all configured entity type mapping fragments.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The entity type.
+ /// The configured entity type mapping fragments.
+ public static IEnumerable GetMappingFragments(this IMutableEntityType entityType)
+ => EntityTypeMappingFragment.Get(entityType)?.Cast()
+ ?? Enumerable.Empty();
+
+ ///
+ ///
+ /// Returns all configured entity type mapping fragments.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The entity type.
+ /// The configured entity type mapping fragments.
+ public static IEnumerable GetMappingFragments(this IConventionEntityType entityType)
+ => EntityTypeMappingFragment.Get(entityType)?.Cast()
+ ?? Enumerable.Empty();
+
///
///
/// Returns all configured entity type mapping fragments.
@@ -857,6 +887,75 @@ public static IEnumerable GetMappingFragments(this I
=> EntityTypeMappingFragment.Get(entityType)?.Cast()
?? Enumerable.Empty();
+ ///
+ ///
+ /// Returns all configured entity type mapping fragments of the given type.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The entity type.
+ /// The type of store object to get the mapping fragments for.
+ /// The configured entity type mapping fragments.
+ public static IEnumerable GetMappingFragments(
+ this IReadOnlyEntityType entityType, StoreObjectType storeObjectType)
+ {
+ var fragments = EntityTypeMappingFragment.Get(entityType);
+ return fragments == null
+ ? Enumerable.Empty()
+ : fragments.Where(f => f.StoreObject.StoreObjectType == storeObjectType);
+ }
+
+ ///
+ ///
+ /// Returns all configured entity type mapping fragments of the given type.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The entity type.
+ /// The type of store object to get the mapping fragments for.
+ /// The configured entity type mapping fragments.
+ public static IEnumerable GetMappingFragments(
+ this IMutableEntityType entityType, StoreObjectType storeObjectType)
+ => GetMappingFragments((IReadOnlyEntityType)entityType, storeObjectType).Cast();
+
+ ///
+ ///
+ /// Returns all configured entity type mapping fragments of the given type.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The entity type.
+ /// The type of store object to get the mapping fragments for.
+ /// The configured entity type mapping fragments.
+ public static IEnumerable GetMappingFragments(
+ this IConventionEntityType entityType, StoreObjectType storeObjectType)
+ => GetMappingFragments((IReadOnlyEntityType)entityType, storeObjectType).Cast();
+
+ ///
+ ///
+ /// Returns all configured entity type mapping fragments of the given type.
+ ///
+ ///
+ /// This method is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ /// The entity type.
+ /// The type of store object to get the mapping fragments for.
+ /// The configured entity type mapping fragments.
+ public static IEnumerable GetMappingFragments(
+ this IEntityType entityType, StoreObjectType storeObjectType)
+ => GetMappingFragments((IReadOnlyEntityType)entityType, storeObjectType).Cast();
+
///
///
/// Returns the entity type mapping for a particular table-like store object.
@@ -1019,46 +1118,22 @@ public static IEnumerable FindRowInternalForeignKeys(
foreach (var foreignKey in entityType.GetForeignKeys())
{
- var principalEntityType = foreignKey.PrincipalEntityType;
if (!foreignKey.PrincipalKey.IsPrimaryKey()
- || principalEntityType == foreignKey.DeclaringEntityType
- || !foreignKey.IsUnique
-#pragma warning disable EF1001 // Internal EF Core API usage.
- || !PropertyListComparer.Instance.Equals(foreignKey.Properties, primaryKey.Properties))
-#pragma warning restore EF1001 // Internal EF Core API usage.
+ || foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
+ || !foreignKey.Properties.SequenceEqual(primaryKey.Properties)
+ || !IsMapped(foreignKey, storeObject))
{
continue;
}
- switch (storeObject.StoreObjectType)
- {
- case StoreObjectType.Table:
- if (storeObject.Name == principalEntityType.GetTableName()
- && storeObject.Schema == principalEntityType.GetSchema())
- {
- yield return foreignKey;
- }
-
- break;
- case StoreObjectType.View:
- if (storeObject.Name == principalEntityType.GetViewName()
- && storeObject.Schema == principalEntityType.GetViewSchema())
- {
- yield return foreignKey;
- }
-
- break;
- case StoreObjectType.Function:
- if (storeObject.Name == principalEntityType.GetFunctionName())
- {
- yield return foreignKey;
- }
-
- break;
- default:
- throw new NotSupportedException(storeObject.StoreObjectType.ToString());
- }
+ yield return foreignKey;
}
+
+ static bool IsMapped(IReadOnlyForeignKey foreignKey, StoreObjectIdentifier storeObject)
+ => (StoreObjectIdentifier.Create(foreignKey.DeclaringEntityType, storeObject.StoreObjectType) == storeObject
+ || foreignKey.DeclaringEntityType.GetMappingFragments(storeObject.StoreObjectType).Any(f => f.StoreObject == storeObject))
+ && (StoreObjectIdentifier.Create(foreignKey.PrincipalEntityType, storeObject.StoreObjectType) == storeObject
+ || foreignKey.PrincipalEntityType.GetMappingFragments(storeObject.StoreObjectType).Any(f => f.StoreObject == storeObject));
}
///
diff --git a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs
index 5b31d18cea6..123e35010c7 100644
--- a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
@@ -44,18 +45,7 @@ public static class RelationalForeignKeyExtensions
this IReadOnlyForeignKey foreignKey,
in StoreObjectIdentifier storeObject,
in StoreObjectIdentifier principalStoreObject)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table
- || principalStoreObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- var annotation = foreignKey.FindAnnotation(RelationalAnnotationNames.Name);
- return annotation != null
- ? (string?)annotation.Value
- : foreignKey.GetDefaultName(storeObject, principalStoreObject);
- }
+ => foreignKey.GetConstraintName(storeObject, principalStoreObject, null);
///
/// Returns the default constraint name that would be used for this foreign key.
@@ -101,78 +91,7 @@ public static class RelationalForeignKeyExtensions
this IReadOnlyForeignKey foreignKey,
in StoreObjectIdentifier storeObject,
in StoreObjectIdentifier principalStoreObject)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table
- || principalStoreObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- var propertyNames = foreignKey.Properties.GetColumnNames(storeObject);
- var principalPropertyNames = foreignKey.PrincipalKey.Properties.GetColumnNames(principalStoreObject);
- if (propertyNames == null
- || principalPropertyNames == null)
- {
- return null;
- }
-
- var rootForeignKey = foreignKey;
-
- // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
- // Using a hashset is detrimental to the perf when there are no cycles
- for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
- {
- IReadOnlyForeignKey? linkedForeignKey = null;
- foreach (var otherForeignKey in rootForeignKey.DeclaringEntityType
- .FindRowInternalForeignKeys(storeObject)
- .SelectMany(fk => fk.PrincipalEntityType.GetForeignKeys()))
- {
- if (principalStoreObject.Name == otherForeignKey.PrincipalEntityType.GetTableName()
- && principalStoreObject.Schema == otherForeignKey.PrincipalEntityType.GetSchema())
- {
- var otherColumnNames = otherForeignKey.Properties.GetColumnNames(storeObject);
- var otherPrincipalColumnNames = otherForeignKey.PrincipalKey.Properties.GetColumnNames(principalStoreObject);
- if (otherColumnNames != null
- && otherPrincipalColumnNames != null
- && propertyNames.SequenceEqual(otherColumnNames)
- && principalPropertyNames.SequenceEqual(otherPrincipalColumnNames))
- {
- linkedForeignKey = otherForeignKey;
- break;
- }
- }
- }
-
- if (linkedForeignKey == null)
- {
- break;
- }
-
- rootForeignKey = linkedForeignKey;
- }
-
- if (rootForeignKey != foreignKey)
- {
- return rootForeignKey.GetConstraintName(storeObject, principalStoreObject);
- }
-
- if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy
- && foreignKey.PrincipalEntityType.GetDerivedTypes().Any(et => StoreObjectIdentifier.Create(et, StoreObjectType.Table) != null))
- {
- return null;
- }
-
- var baseName = new StringBuilder()
- .Append("FK_")
- .Append(storeObject.Name)
- .Append('_')
- .Append(principalStoreObject.Name)
- .Append('_')
- .AppendJoin(propertyNames, "_")
- .ToString();
-
- return Uniquifier.Truncate(baseName, foreignKey.DeclaringEntityType.Model.GetMaxIdentifierLength());
- }
+ => foreignKey.GetDefaultName(storeObject, principalStoreObject, null);
///
/// Sets the foreign key constraint name.
diff --git a/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs
index fedf1762784..aecd8fe5909 100644
--- a/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-
-
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
diff --git a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs
index 7efc8078c15..76492dabbe6 100644
--- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
@@ -33,11 +34,7 @@ public static class RelationalIndexExtensions
/// The identifier of the store object.
/// The name of the index in the database.
public static string? GetDatabaseName(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject)
- => storeObject.StoreObjectType != StoreObjectType.Table
- ? null
- : (string?)index[RelationalAnnotationNames.Name]
- ?? index.Name
- ?? index.GetDefaultDatabaseName(storeObject);
+ => index.GetDatabaseName(storeObject, null);
///
/// Returns the default name that would be used for this index.
@@ -69,61 +66,8 @@ public static class RelationalIndexExtensions
/// The identifier of the store object.
/// The default name that would be used for this index.
public static string? GetDefaultDatabaseName(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- var columnNames = index.Properties.GetColumnNames(storeObject);
- if (columnNames == null)
- {
- return null;
- }
-
- var rootIndex = index;
-
- // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
- // Using a hashset is detrimental to the perf when there are no cycles
- for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
- {
- IReadOnlyIndex? linkedIndex = null;
- foreach (var otherIndex in rootIndex.DeclaringEntityType
- .FindRowInternalForeignKeys(storeObject)
- .SelectMany(fk => fk.PrincipalEntityType.GetIndexes()))
- {
- var otherColumnNames = otherIndex.Properties.GetColumnNames(storeObject);
- if ((otherColumnNames != null)
- && otherColumnNames.SequenceEqual(columnNames))
- {
- linkedIndex = otherIndex;
- break;
- }
- }
-
- if (linkedIndex == null)
- {
- break;
- }
-
- rootIndex = linkedIndex;
- }
-
- if (rootIndex != index)
- {
- return rootIndex.GetDatabaseName(storeObject);
- }
-
- var baseName = new StringBuilder()
- .Append("IX_")
- .Append(storeObject.Name)
- .Append('_')
- .AppendJoin(columnNames, "_")
- .ToString();
-
- return Uniquifier.Truncate(baseName, index.DeclaringEntityType.Model.GetMaxIdentifierLength());
- }
-
+ => index.GetDefaultDatabaseName(storeObject, null);
+
///
/// Sets the name of the index in the database.
///
diff --git a/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs
index fbbeb6e93cf..af4468795a8 100644
--- a/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-
-
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
diff --git a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs
index 0d5381af63b..200d63e33da 100644
--- a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
@@ -32,22 +33,7 @@ public static class RelationalKeyExtensions
/// The identifier of the containing store object.
/// The key constraint name for this key.
public static string? GetName(this IReadOnlyKey key, in StoreObjectIdentifier storeObject)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- foreach (var containingType in key.DeclaringEntityType.GetDerivedTypesInclusive())
- {
- if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject)
- {
- return (string?)key[RelationalAnnotationNames.Name] ?? key.GetDefaultName(storeObject);
- }
- }
-
- return null;
- }
+ => key.GetName(storeObject, null);
///
/// Returns the default key constraint name that would be used for this key.
@@ -81,89 +67,7 @@ public static class RelationalKeyExtensions
/// The identifier of the containing store object.
/// The default key constraint name that would be used for this key.
public static string? GetDefaultName(this IReadOnlyKey key, in StoreObjectIdentifier storeObject)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- string? name;
- if (key.IsPrimaryKey())
- {
- var rootKey = key;
- // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
- // Using a hashset is detrimental to the perf when there are no cycles
- for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
- {
- var linkingFk = rootKey!.DeclaringEntityType.FindRowInternalForeignKeys(storeObject)
- .FirstOrDefault();
- if (linkingFk == null)
- {
- break;
- }
-
- rootKey = linkingFk.PrincipalEntityType.FindPrimaryKey();
- }
-
- if (rootKey != null
- && rootKey != key)
- {
- return rootKey.GetName(storeObject);
- }
-
- name = "PK_" + storeObject.Name;
- }
- else
- {
- var columnNames = key.Properties.GetColumnNames(storeObject);
- if (columnNames == null)
- {
- return null;
- }
-
- var rootKey = key;
-
- // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
- // Using a hashset is detrimental to the perf when there are no cycles
- for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
- {
- IReadOnlyKey? linkedKey = null;
- foreach (var otherKey in rootKey.DeclaringEntityType
- .FindRowInternalForeignKeys(storeObject)
- .SelectMany(fk => fk.PrincipalEntityType.GetKeys()))
- {
- var otherColumnNames = otherKey.Properties.GetColumnNames(storeObject);
- if ((otherColumnNames != null)
- && otherColumnNames.SequenceEqual(columnNames))
- {
- linkedKey = otherKey;
- break;
- }
- }
-
- if (linkedKey == null)
- {
- break;
- }
-
- rootKey = linkedKey;
- }
-
- if (rootKey != key)
- {
- return rootKey.GetName(storeObject);
- }
-
- name = new StringBuilder()
- .Append("AK_")
- .Append(storeObject.Name)
- .Append('_')
- .AppendJoin(columnNames, "_")
- .ToString();
- }
-
- return Uniquifier.Truncate(name, key.DeclaringEntityType.Model.GetMaxIdentifierLength());
- }
+ => key.GetDefaultName(storeObject, null);
///
/// Sets the key constraint name for this key.
diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
index ce5b852aaf3..7dff017dcce 100644
--- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
@@ -115,21 +115,24 @@ public static string GetColumnName(this IReadOnlyProperty property)
return null;
}
}
- else if (property.DeclaringEntityType.GetMappingFragments().Any())
+ else
{
- if (overrides == null
- && (declaringStoreObject != storeObject
- || property.DeclaringEntityType.GetMappingFragments()
- .Any(f => property.FindOverrides(f.StoreObject) != null)))
+ var fragments = property.DeclaringEntityType.GetMappingFragments(storeObject.StoreObjectType).ToList();
+ if (fragments.Count > 0)
{
+ if (overrides == null
+ && (declaringStoreObject != storeObject
+ || fragments.Any(f => property.FindOverrides(f.StoreObject) != null)))
+ {
+ return null;
+ }
+ }
+ else if (declaringStoreObject != storeObject)
+ {
return null;
}
}
- else if (declaringStoreObject != storeObject)
- {
- return null;
- }
}
}
@@ -181,6 +184,7 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property, in St
var entityType = property.DeclaringEntityType;
StringBuilder? builder = null;
+ var currentStoreObject = storeObject;
while (true)
{
var ownership = entityType.GetForeignKeys().SingleOrDefault(fk => fk.IsOwnership);
@@ -189,39 +193,10 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property, in St
break;
}
- var name = storeObject.Name;
- var schema = storeObject.Schema;
var ownerType = ownership.PrincipalEntityType;
- switch (storeObject.StoreObjectType)
- {
- case StoreObjectType.Table:
- if (name != ownerType.GetTableName()
- || schema != ownerType.GetSchema())
- {
- entityType = null;
- }
-
- break;
- case StoreObjectType.View:
- if (name != ownerType.GetViewName()
- || schema != ownerType.GetViewSchema())
- {
- entityType = null;
- }
-
- break;
- case StoreObjectType.Function:
- if (name != ownerType.GetFunctionName())
- {
- entityType = null;
- }
-
- break;
- default:
- throw new NotSupportedException(storeObject.StoreObjectType.ToString());
- }
-
- if (entityType == null)
+ if (StoreObjectIdentifier.Create(ownerType, currentStoreObject.StoreObjectType) != currentStoreObject
+ && ownerType.GetMappingFragments(storeObject.StoreObjectType)
+ .All(f => f.StoreObject != currentStoreObject))
{
break;
}
@@ -1624,14 +1599,18 @@ public static IEnumerable GetMappedStoreObjects(
yield break;
}
- foreach (var fragment in declaringType.GetMappingFragments())
+ foreach (var fragment in declaringType.GetMappingFragments(storeObjectType))
{
- if (fragment.StoreObject.StoreObjectType == storeObjectType
- && property.GetColumnName(fragment.StoreObject) != null)
+ if (property.GetColumnName(fragment.StoreObject) != null)
{
yield return fragment.StoreObject;
}
}
+
+ if (declaringType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
+ {
+ yield break;
+ }
foreach (var derivedType in declaringType.GetDerivedTypes())
{
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index f4faed9457d..722d9e77ebb 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -218,7 +218,7 @@ protected virtual void ValidateBoolsWithDefaults(
continue;
}
- if (StoreObjectIdentifier.Create(property.DeclaringEntityType, StoreObjectType.Table) is StoreObjectIdentifier table
+ if (StoreObjectIdentifier.Create(property.DeclaringEntityType, StoreObjectType.Table) is { } table
&& (IsNotNullAndFalse(property.GetDefaultValue(table))
|| property.GetDefaultValueSql(table) != null))
{
@@ -270,13 +270,13 @@ protected virtual void ValidateSharedTableCompatibility(
var tables = new Dictionary>();
foreach (var entityType in model.GetEntityTypes())
{
- var tableName = entityType.GetTableName();
- if (tableName == null)
+ var tableId = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
+ if (tableId == null)
{
continue;
}
- var table = StoreObjectIdentifier.Table(tableName, entityType.GetSchema());
+ var table = tableId.Value;
if (!tables.TryGetValue(table, out var mappedTypes))
{
mappedTypes = new List();
@@ -288,7 +288,7 @@ protected virtual void ValidateSharedTableCompatibility(
foreach (var (table, mappedTypes) in tables)
{
- ValidateSharedTableCompatibility(mappedTypes, table.Name, table.Schema, logger);
+ ValidateSharedTableCompatibility(mappedTypes, table, logger);
ValidateSharedColumnsCompatibility(mappedTypes, table, logger);
ValidateSharedKeysCompatibility(mappedTypes, table, logger);
ValidateSharedForeignKeysCompatibility(mappedTypes, table, logger);
@@ -362,16 +362,17 @@ protected virtual void ValidateSharedTableCompatibility(
foreach (var foreignKey in entityType.FindForeignKeys(entityType.FindPrimaryKey()!.Properties))
{
var principalEntityType = foreignKey.PrincipalEntityType;
- if (!mappedTypes.Contains(principalEntityType))
+ if (foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
+ || !mappedTypes.Contains(principalEntityType))
{
continue;
}
list.Add(principalEntityType);
- var (entityTypes, innerOptional) = GetPrincipalEntityTypes(principalEntityType.GetRootType());
+ var (entityTypes, _) = GetPrincipalEntityTypes(principalEntityType.GetRootType());
list.AddRange(entityTypes);
- optional |= !foreignKey.IsRequiredDependent | innerOptional;
+ optional |= !foreignKey.IsRequiredDependent;
}
tuple = (list, optional);
@@ -387,13 +388,11 @@ protected virtual void ValidateSharedTableCompatibility(
/// Validates the compatibility of entity types sharing a given table.
///
/// The mapped entity types.
- /// The table name.
- /// The schema.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateSharedTableCompatibility(
IReadOnlyList mappedTypes,
- string tableName,
- string? schema,
+ in StoreObjectIdentifier storeObject,
IDiagnosticsLogger logger)
{
if (mappedTypes.Count == 1)
@@ -401,7 +400,6 @@ protected virtual void ValidateSharedTableCompatibility(
return;
}
- var storeObject = StoreObjectIdentifier.Table(tableName, schema);
var unvalidatedTypes = new HashSet(mappedTypes);
IEntityType? root = null;
foreach (var mappedType in mappedTypes)
@@ -416,7 +414,8 @@ protected virtual void ValidateSharedTableCompatibility(
&& (mappedType.FindForeignKeys(primaryKey.Properties)
.FirstOrDefault(
fk => fk.PrincipalKey.IsPrimaryKey()
- && unvalidatedTypes.Contains(fk.PrincipalEntityType)) is IForeignKey linkingFK))
+ && !fk.PrincipalEntityType.IsAssignableFrom(fk.DeclaringEntityType)
+ && unvalidatedTypes.Contains(fk.PrincipalEntityType)) is { } linkingFK))
{
if (mappedType.BaseType != null)
{
@@ -526,7 +525,7 @@ protected virtual void ValidateSharedTableCompatibility(
Check.DebugAssert(root != null, "root is null");
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableNoRelationship(
- tableName,
+ storeObject.DisplayName(),
invalidEntityType.DisplayName(),
root.DisplayName()));
}
@@ -874,10 +873,8 @@ protected virtual void ValidateCompatible(
var typeMapping = property.GetRelationalTypeMapping();
var duplicateTypeMapping = duplicateProperty.GetRelationalTypeMapping();
- var currentTypeString = property.GetColumnType(storeObject)
- ?? typeMapping.StoreType;
- var previousTypeString = duplicateProperty.GetColumnType(storeObject)
- ?? duplicateTypeMapping.StoreType;
+ var currentTypeString = property.GetColumnType(storeObject);
+ var previousTypeString = duplicateProperty.GetColumnType(storeObject);
if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
@@ -1075,27 +1072,9 @@ protected virtual void ValidateSharedForeignKeysCompatibility(
continue;
}
- var foreignKeyName = foreignKey.GetConstraintName(storeObject, principalTable.Value);
+ var foreignKeyName = foreignKey.GetConstraintName(storeObject, principalTable.Value, logger);
if (foreignKeyName == null)
{
- if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
- {
- logger.ForeignKeyTpcPrincipalWarning(foreignKey);
- }
-
- var derivedTables = foreignKey.DeclaringEntityType.GetDerivedTypes()
- .Select(t => StoreObjectIdentifier.Create(t, StoreObjectType.Table))
- .Where(t => t != null);
- if (foreignKey.GetConstraintName() != null
- && derivedTables.All(
- t => foreignKey.GetConstraintName(
- t!.Value,
- principalTable.Value)
- == null))
- {
- logger.ForeignKeyPropertiesMappedToUnrelatedTables(foreignKey);
- }
-
continue;
}
@@ -1139,7 +1118,7 @@ protected virtual void ValidateSharedIndexesCompatibility(
var indexMappings = new Dictionary();
foreach (var index in mappedTypes.SelectMany(et => et.GetDeclaredIndexes()))
{
- var indexName = index.GetDatabaseName(storeObject);
+ var indexName = index.GetDatabaseName(storeObject, logger);
if (indexName == null)
{
continue;
@@ -1185,7 +1164,7 @@ protected virtual void ValidateSharedKeysCompatibility(
var keyMappings = new Dictionary();
foreach (var key in mappedTypes.SelectMany(et => et.GetDeclaredKeys()))
{
- var keyName = key.GetName(storeObject);
+ var keyName = key.GetName(storeObject, logger);
if (keyName == null)
{
continue;
@@ -1379,12 +1358,6 @@ protected override void ValidateInheritanceMapping(
{
logger.TpcStoreGeneratedIdentityWarning(storeGeneratedProperty);
}
-
- foreach (var fk in entityType.GetDeclaredReferencingForeignKeys())
- {
- AssertNonInternal(fk, StoreObjectType.View);
- AssertNonInternal(fk, StoreObjectType.Table);
- }
}
else if (primaryKey == null)
{
@@ -1420,32 +1393,6 @@ protected override void ValidateInheritanceMapping(
}
}
}
-
- static void AssertNonInternal(IForeignKey foreignKey, StoreObjectType storeObjectType)
- {
- if (!foreignKey.PrincipalKey.IsPrimaryKey()
- || foreignKey.PrincipalEntityType == foreignKey.DeclaringEntityType
- || !foreignKey.IsUnique
- || foreignKey.DeclaringEntityType.FindPrimaryKey() == null
-#pragma warning disable EF1001 // Internal EF Core API usage.
- || !PropertyListComparer.Instance.Equals(foreignKey.Properties, foreignKey.DeclaringEntityType.FindPrimaryKey()!.Properties))
-#pragma warning restore EF1001 // Internal EF Core API usage.
- {
- return;
- }
-
- var storeObjectId = StoreObjectIdentifier.Create(foreignKey.DeclaringEntityType, storeObjectType);
- if (storeObjectId == null
- || storeObjectId != StoreObjectIdentifier.Create(foreignKey.PrincipalEntityType, storeObjectType))
- {
- return;
- }
-
- throw new InvalidOperationException(RelationalStrings.TpcTableSharing(
- foreignKey.DeclaringEntityType.DisplayName(),
- storeObjectId.Value.DisplayName(),
- foreignKey.PrincipalEntityType.DisplayName()));
- }
}
///
@@ -1469,6 +1416,7 @@ protected virtual void ValidateMappingStrategy(IEntityType entityType, string? m
private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTables)
{
+ var isTpc = rootEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy;
var derivedTypes = new Dictionary<(string, string?), IEntityType>();
foreach (var entityType in rootEntityType.GetDerivedTypesInclusive())
{
@@ -1489,17 +1437,31 @@ private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTa
entityType.DisplayName(), otherType.DisplayName(), entityType.GetSchemaQualifiedViewName()));
}
+ if (isTpc)
+ {
+ var storeObject = StoreObjectIdentifier.Create(entityType, forTables ? StoreObjectType.Table : StoreObjectType.View)!;
+ var rowInternalFk = entityType.FindDeclaredReferencingRowInternalForeignKeys(storeObject.Value)
+ .FirstOrDefault();
+ if (rowInternalFk != null
+ && entityType.GetDirectlyDerivedTypes().Any())
+ {
+ throw new InvalidOperationException(RelationalStrings.TpcTableSharing(
+ rowInternalFk.DeclaringEntityType.DisplayName(),
+ storeObject.Value.DisplayName(),
+ rowInternalFk.PrincipalEntityType.DisplayName()));
+ }
+ }
+
derivedTypes[(name, schema)] = entityType;
}
- var storeObject = StoreObjectIdentifier.Create(rootEntityType, forTables ? StoreObjectType.Table : StoreObjectType.View);
- if (storeObject == null)
+ var rootStoreObject = StoreObjectIdentifier.Create(rootEntityType, forTables ? StoreObjectType.Table : StoreObjectType.View);
+ if (rootStoreObject == null)
{
return;
}
- var internalForeignKey = rootEntityType.FindRowInternalForeignKeys(storeObject.Value).FirstOrDefault();
- if (internalForeignKey != null
+ if (rootEntityType.FindRowInternalForeignKeys(rootStoreObject.Value).Any()
&& derivedTypes.Count > 1
&& rootEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
{
@@ -1507,7 +1469,7 @@ private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTa
var (derivedName, derivedSchema) = derivedTypePair.Key;
throw new InvalidOperationException(RelationalStrings.TpcTableSharingDependent(
rootEntityType.DisplayName(),
- storeObject.Value.DisplayName(),
+ rootStoreObject.Value.DisplayName(),
derivedTypePair.Value.DisplayName(),
derivedSchema == null ? derivedName : $"{derivedSchema}.{derivedName}"));
}
@@ -1549,6 +1511,11 @@ private static void ValidateTphMapping(IEntityType rootEntityType, bool forTable
}
}
+ ///
+ protected override bool IsRedundant(IForeignKey foreignKey)
+ => base.IsRedundant(foreignKey)
+ && !foreignKey.DeclaringEntityType.GetMappingFragments().Any();
+
///
/// Validates the entity type mapping fragments.
///
@@ -1573,6 +1540,7 @@ protected virtual void ValidateMappingFragments(
RelationalStrings.EntitySplittingHierarchy(entityType.DisplayName(), fragments.First().StoreObject.DisplayName()));
}
+ // dependent table splitting and main fragment is not
var anyTableFragments = false;
var anyViewFragments = false;
foreach (var fragment in fragments)
@@ -1592,6 +1560,16 @@ protected virtual void ValidateMappingFragments(
entityType.DisplayName(), fragment.StoreObject.DisplayName()));
}
+ var unmatchedLeafRowInternalFk = entityType.FindRowInternalForeignKeys(fragment.StoreObject)
+ .FirstOrDefault(
+ fk => entityType.FindRowInternalForeignKeys(mainStoreObject.Value)
+ .All(mainFk => mainFk.PrincipalEntityType != fk.PrincipalEntityType));
+ if (unmatchedLeafRowInternalFk != null)
+ {
+ throw new InvalidOperationException(RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(
+ entityType.DisplayName(), fragment.StoreObject.DisplayName(), unmatchedLeafRowInternalFk.PrincipalEntityType.DisplayName()));
+ }
+
var propertiesFound = false;
foreach (var property in entityType.GetProperties())
{
@@ -1749,7 +1727,7 @@ private static IEnumerable GetAllMappedStoreObjects(
yield break;
}
- foreach (var fragment in property.DeclaringEntityType.GetMappingFragments())
+ foreach (var fragment in property.DeclaringEntityType.GetMappingFragments(storeObjectType))
{
yield return fragment.StoreObject;
}
@@ -1783,17 +1761,24 @@ private static IEnumerable GetAllMappedStoreObjects(
if (declaringStoreObject != null)
{
- if (property.DeclaringEntityType.GetMappingFragments().Any())
+ var fragments = property.DeclaringEntityType.GetMappingFragments(storeObjectType).ToList();
+ if (fragments.Count > 0)
{
- foreach (var fragment in property.DeclaringEntityType.GetMappingFragments())
+ var overrides = RelationalPropertyOverrides.Find(property, declaringStoreObject.Value);
+ if (overrides != null)
{
- var overrides = RelationalPropertyOverrides.Find(property, fragment.StoreObject);
+ yield return declaringStoreObject.Value;
+ }
+
+ foreach (var fragment in fragments)
+ {
+ overrides = RelationalPropertyOverrides.Find(property, fragment.StoreObject);
if (overrides != null)
{
yield return fragment.StoreObject;
- yield break;
}
}
+ yield break;
}
yield return declaringStoreObject.Value;
@@ -1826,7 +1811,6 @@ private static IEnumerable GetAllMappedStoreObjects(
{
queue.Enqueue(containingType);
}
- continue;
}
}
}
@@ -1843,93 +1827,16 @@ protected virtual void ValidateIndexProperties(
{
foreach (var entityType in model.GetEntityTypes())
{
- foreach (var index in entityType.GetDeclaredIndexes()
- .Where(i => ConfigurationSource.Convention != ((IConventionIndex)i).GetConfigurationSource()))
- {
- IProperty? propertyNotMappedToAnyTable = null;
- Tuple>? firstPropertyTables = null;
- Tuple>? lastPropertyTables = null;
- HashSet<(string Table, string? Schema)>? overlappingTables = null;
- foreach (var property in index.Properties)
- {
- var tablesMappedToProperty = property.DeclaringEntityType.GetDerivedTypesInclusive()
- .Select(t => (t.GetTableName(), t.GetSchema())).Distinct()
- .Where(n => n.Item1 != null && property.GetColumnName(StoreObjectIdentifier.Table(n.Item1, n.Item2)) != null)!
- .ToList<(string Table, string? Schema)>();
- if (tablesMappedToProperty.Count == 0)
- {
- propertyNotMappedToAnyTable = property;
- overlappingTables = null;
-
- if (firstPropertyTables != null)
- {
- // Property is not mapped but we already found a property that is mapped.
- break;
- }
-
- continue;
- }
-
- if (firstPropertyTables == null)
- {
- firstPropertyTables =
- new Tuple>(property.Name, tablesMappedToProperty);
- }
- else
- {
- lastPropertyTables =
- new Tuple>(property.Name, tablesMappedToProperty);
- }
-
- if (propertyNotMappedToAnyTable != null)
- {
- // Property is mapped but we already found a property that is not mapped.
- overlappingTables = null;
- break;
- }
-
- if (overlappingTables == null)
- {
- overlappingTables = new HashSet<(string Table, string? Schema)>(tablesMappedToProperty);
- }
- else
- {
- overlappingTables.IntersectWith(tablesMappedToProperty);
- if (overlappingTables.Count == 0)
- {
- break;
- }
- }
- }
+ if (entityType.GetTableName() != null)
+ {
+ continue;
+ }
- if (overlappingTables == null)
- {
- if (firstPropertyTables == null)
- {
- logger.AllIndexPropertiesNotToMappedToAnyTable(
- entityType,
- index);
- }
- else
- {
- logger.IndexPropertiesBothMappedAndNotMappedToTable(
- entityType,
- index,
- propertyNotMappedToAnyTable!.Name);
- }
- }
- else if (overlappingTables.Count == 0)
+ foreach (var index in entityType.GetDeclaredIndexes())
+ {
+ if (ConfigurationSource.Convention != ((IConventionIndex)index).GetConfigurationSource())
{
- Check.DebugAssert(firstPropertyTables != null, nameof(firstPropertyTables));
- Check.DebugAssert(lastPropertyTables != null, nameof(lastPropertyTables));
-
- logger.IndexPropertiesMappedToNonOverlappingTables(
- entityType,
- index,
- firstPropertyTables.Item1,
- firstPropertyTables.Item2,
- lastPropertyTables.Item1,
- lastPropertyTables.Item2);
+ index.GetDatabaseName(StoreObjectIdentifier.Table(""), logger);
}
}
}
@@ -1963,7 +1870,7 @@ protected virtual void ValidateTriggers(
throw new InvalidOperationException(
RelationalStrings.TriggerWithMismatchedTable(
trigger.ModelName,
- (trigger.TableName!, trigger.TableSchema).FormatTable(),
+ (trigger.TableName, trigger.TableSchema).FormatTable(),
entityType.DisplayName(),
entityType.GetSchemaQualifiedTableName())
);
diff --git a/src/EFCore.Relational/Metadata/Conventions/EntitySplittingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/EntitySplittingConvention.cs
new file mode 100644
index 00000000000..ee8e4fc30d6
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Conventions/EntitySplittingConvention.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+///
+/// A convention that creates linking relationships for entity splitting.
+///
+///
+/// See Model building conventions and
+/// Entity type hierarchy mapping for more information and examples.
+///
+public class EntitySplittingConvention : IModelFinalizingConvention
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// Parameter object containing dependencies for this convention.
+ /// Parameter object containing relational dependencies for this convention.
+ public EntitySplittingConvention(
+ ProviderConventionSetBuilderDependencies dependencies,
+ RelationalConventionSetBuilderDependencies relationalDependencies)
+ {
+ Dependencies = dependencies;
+ RelationalDependencies = relationalDependencies;
+ }
+
+ ///
+ /// Dependencies for this service.
+ ///
+ protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }
+
+ ///
+ /// Relational provider-specific dependencies for this service.
+ ///
+ protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
+
+ ///
+ public virtual void ProcessModelFinalizing(
+ IConventionModelBuilder modelBuilder,
+ IConventionContext context)
+ {
+ foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
+ {
+ if (!entityType.GetMappingFragments().Any()
+ || entityType.GetTableName() == null)
+ {
+ continue;
+ }
+
+ var pk = entityType.FindPrimaryKey();
+ if (pk != null
+ && !entityType.FindDeclaredForeignKeys(pk.Properties)
+ .Any(fk => fk.PrincipalKey.IsPrimaryKey()
+ && fk.PrincipalEntityType.IsAssignableFrom(entityType)
+ && fk.PrincipalEntityType != entityType))
+ {
+ entityType.Builder.HasRelationship(entityType, pk.Properties, pk)
+ ?.IsUnique(true)
+ ?.IsRequiredDependent(true);
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index 3937f1281e1..a14ff223ec2 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -112,6 +112,7 @@ public override ConventionSet CreateConventionSet()
conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention);
conventionSet.ModelFinalizingConventions.Add(tableNameFromDbSetConvention);
conventionSet.ModelFinalizingConventions.Add(storeGenerationConvention);
+ conventionSet.ModelFinalizingConventions.Add(new EntitySplittingConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(new EntityTypeHierarchyMappingConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(new SequenceUniquificationConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(new SharedTableConvention(Dependencies, RelationalDependencies));
diff --git a/src/EFCore.Relational/Metadata/ITableMappingBase.cs b/src/EFCore.Relational/Metadata/ITableMappingBase.cs
index c47ad15840f..10722e17882 100644
--- a/src/EFCore.Relational/Metadata/ITableMappingBase.cs
+++ b/src/EFCore.Relational/Metadata/ITableMappingBase.cs
@@ -28,15 +28,15 @@ public interface ITableMappingBase : IAnnotatable
///
/// Gets the value indicating whether this is the mapping for the principal entity type
- /// if the table-like object is shared.
+ /// if the table-like object is shared. is the table-like object is not shared.
///
- bool IsSharedTablePrincipal { get; }
+ bool? IsSharedTablePrincipal { get; }
///
/// Gets the value indicating whether this is the mapping for the principal table-like object
- /// if the entity type is split.
+ /// if the entity type is split. is the entity type is not split.
///
- bool IsSplitEntityTypePrincipal { get; }
+ bool? IsSplitEntityTypePrincipal { get; }
///
/// Gets the value indicating whether the mapped table-like object includes rows for the derived entity types.
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs
index 8bab3bf5e09..437797dcbde 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs
@@ -19,6 +19,42 @@ public static class RelationalEntityTypeExtensions
///
public const int MaxEntityTypesSharingTable = 128;
+ ///
+ /// 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 IEnumerable FindDeclaredReferencingRowInternalForeignKeys(
+ this IEntityType entityType,
+ StoreObjectIdentifier storeObject)
+ {
+ foreach (var foreignKey in entityType.GetDeclaredReferencingForeignKeys())
+ {
+ var dependentPrimaryKey = foreignKey.DeclaringEntityType.FindPrimaryKey();
+ if (dependentPrimaryKey == null)
+ {
+ yield break;
+ }
+
+ if (!foreignKey.PrincipalKey.IsPrimaryKey()
+ || foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
+ || !foreignKey.Properties.SequenceEqual(dependentPrimaryKey.Properties)
+ || !IsMapped(foreignKey, storeObject))
+ {
+ continue;
+ }
+
+ yield return foreignKey;
+ }
+
+ static bool IsMapped(IReadOnlyForeignKey foreignKey, StoreObjectIdentifier storeObject)
+ => (StoreObjectIdentifier.Create(foreignKey.DeclaringEntityType, storeObject.StoreObjectType) == storeObject
+ || foreignKey.DeclaringEntityType.GetMappingFragments(storeObject.StoreObjectType).Any(f => f.StoreObject == storeObject))
+ && (StoreObjectIdentifier.Create(foreignKey.PrincipalEntityType, storeObject.StoreObjectType) == storeObject
+ || foreignKey.PrincipalEntityType.GetMappingFragments(storeObject.StoreObjectType).Any(f => f.StoreObject == storeObject));
+ }
+
///
/// 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.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs
index 96ccf2e0c18..aa6f1b81d99 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
@@ -161,4 +163,176 @@ public static bool AreCompatible(
return true;
}
+
+ ///
+ /// 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? GetConstraintName(
+ this IReadOnlyForeignKey foreignKey,
+ in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier principalStoreObject,
+ IDiagnosticsLogger? logger)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table
+ || principalStoreObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ var defaultName = foreignKey.GetDefaultName(storeObject, principalStoreObject, logger);
+ var annotation = foreignKey.FindAnnotation(RelationalAnnotationNames.Name);
+ return annotation != null && defaultName != null
+ ? (string?)annotation.Value
+ : defaultName;
+ }
+
+ ///
+ /// 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? GetDefaultName(
+ this IReadOnlyForeignKey foreignKey,
+ in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier principalStoreObject,
+ IDiagnosticsLogger? logger)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table
+ || principalStoreObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ var propertyNames = foreignKey.Properties.GetColumnNames(storeObject);
+ var principalPropertyNames = foreignKey.PrincipalKey.Properties.GetColumnNames(principalStoreObject);
+ if (propertyNames == null
+ || principalPropertyNames == null)
+ {
+ if (logger != null)
+ {
+ var principalTable = principalStoreObject;
+ var derivedTables = foreignKey.DeclaringEntityType.GetDerivedTypes()
+ .Select(t => StoreObjectIdentifier.Create(t, StoreObjectType.Table))
+ .Where(t => t != null);
+ if (foreignKey.GetConstraintName() != null
+ && derivedTables.All(
+ t => foreignKey.GetConstraintName(
+ t!.Value,
+ principalTable)
+ == null))
+ {
+ logger.ForeignKeyPropertiesMappedToUnrelatedTables((IForeignKey)foreignKey);
+ }
+ }
+
+ return null;
+ }
+
+ var rootForeignKey = foreignKey;
+
+ // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
+ // Using a hashset is detrimental to the perf when there are no cycles
+ for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
+ {
+ IReadOnlyForeignKey? linkedForeignKey = null;
+ foreach (var otherForeignKey in rootForeignKey.DeclaringEntityType
+ .FindRowInternalForeignKeys(storeObject)
+ .SelectMany(fk => fk.PrincipalEntityType.GetForeignKeys()))
+ {
+ if (principalStoreObject.Name == otherForeignKey.PrincipalEntityType.GetTableName()
+ && principalStoreObject.Schema == otherForeignKey.PrincipalEntityType.GetSchema())
+ {
+ var otherColumnNames = otherForeignKey.Properties.GetColumnNames(storeObject);
+ var otherPrincipalColumnNames = otherForeignKey.PrincipalKey.Properties.GetColumnNames(principalStoreObject);
+ if (otherColumnNames != null
+ && otherPrincipalColumnNames != null
+ && propertyNames.SequenceEqual(otherColumnNames)
+ && principalPropertyNames.SequenceEqual(otherPrincipalColumnNames))
+ {
+ var nameAnnotation = otherForeignKey.FindAnnotation(RelationalAnnotationNames.Name);
+ if (nameAnnotation != null)
+ {
+ return (string?)nameAnnotation.Value;
+ }
+
+ linkedForeignKey = otherForeignKey;
+ break;
+ }
+ }
+ }
+
+ if (linkedForeignKey == null)
+ {
+ break;
+ }
+
+ rootForeignKey = linkedForeignKey;
+ }
+
+ if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy
+ && foreignKey.PrincipalEntityType.GetDerivedTypes().Any(et => StoreObjectIdentifier.Create(et, StoreObjectType.Table) != null))
+ {
+ logger?.ForeignKeyTpcPrincipalWarning((IForeignKey)foreignKey);
+ return null;
+ }
+
+ if (storeObject == principalStoreObject
+ && propertyNames.SequenceEqual(principalPropertyNames))
+ {
+ // Redundant FK
+ return null;
+ }
+
+ if (foreignKey.PrincipalKey.IsPrimaryKey()
+ && foreignKey.DeclaringEntityType.FindPrimaryKey() is IKey pk
+ && foreignKey.Properties.SequenceEqual(pk.Properties))
+ {
+ if (!foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
+ && (StoreObjectIdentifier.Create(foreignKey.DeclaringEntityType, StoreObjectType.Table) != storeObject
+ || StoreObjectIdentifier.Create(foreignKey.PrincipalEntityType, StoreObjectType.Table) != principalStoreObject)
+ && ShareAnyFragments(foreignKey.DeclaringEntityType, foreignKey.PrincipalEntityType))
+ {
+ // Row-internal FK
+ return null;
+ }
+
+ if (foreignKey.PrincipalEntityType == foreignKey.DeclaringEntityType
+ && StoreObjectIdentifier.Create(foreignKey.PrincipalEntityType, StoreObjectType.Table) != principalStoreObject)
+ {
+ // Only create entity-splitting linking FKs to the main fragment
+ return null;
+ }
+ }
+
+ if (foreignKey.DeclaringEntityType.GetMappingStrategy() == RelationalAnnotationNames.TptMappingStrategy
+ && StoreObjectIdentifier.Create(foreignKey.DeclaringEntityType, StoreObjectType.Table) != storeObject
+ && foreignKey.DeclaringEntityType.FindPrimaryKey() is IKey primaryKey
+ && foreignKey.Properties.SequenceEqual(primaryKey.Properties))
+ {
+ // The identifying FK constraint is needed to be created only on the table that corresponds
+ // to the declaring entity type
+ return null;
+ }
+
+ var baseName = new StringBuilder()
+ .Append("FK_")
+ .Append(storeObject.Name)
+ .Append('_')
+ .Append(principalStoreObject.Name)
+ .Append('_')
+ .AppendJoin(propertyNames, "_")
+ .ToString();
+
+ return Uniquifier.Truncate(baseName, foreignKey.DeclaringEntityType.Model.GetMaxIdentifierLength());
+
+ static bool ShareAnyFragments(IReadOnlyEntityType entityType1, IReadOnlyEntityType entityType2)
+ => new[] { StoreObjectIdentifier.Create(entityType1, StoreObjectType.Table)!.Value }
+ .Concat(entityType1.GetMappingFragments(StoreObjectType.Table).Select(f => f.StoreObject))
+ .Intersect(new[] { StoreObjectIdentifier.Create(entityType2, StoreObjectType.Table)!.Value }
+ .Concat(entityType2.GetMappingFragments(StoreObjectType.Table).Select(f => f.StoreObject))).Any();
+ }
}
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.cs
index 2dd404a4500..74868b04a53 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
@@ -121,4 +123,179 @@ public static bool AreCompatible(
return true;
}
+
+ ///
+ /// 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? GetDatabaseName(
+ this IReadOnlyIndex index,
+ in StoreObjectIdentifier storeObject,
+ IDiagnosticsLogger? logger)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ var defaultName = index.GetDefaultDatabaseName(storeObject, logger);
+ var annotation = index.FindAnnotation(RelationalAnnotationNames.Name);
+ return annotation != null && defaultName != null
+ ? (string?)annotation.Value
+ : defaultName != null
+ ? index.Name ?? defaultName
+ : defaultName;
+ }
+
+ ///
+ /// 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? GetDefaultDatabaseName(
+ this IReadOnlyIndex index,
+ in StoreObjectIdentifier storeObject,
+ IDiagnosticsLogger? logger)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ var columnNames = index.Properties.GetColumnNames(storeObject);
+ if (columnNames == null)
+ {
+ if (logger != null
+ && ((IConventionIndex)index).GetConfigurationSource() != ConfigurationSource.Convention)
+ {
+ IReadOnlyProperty? propertyNotMappedToAnyTable = null;
+ (string, List)? firstPropertyTables = null;
+ (string, List)? lastPropertyTables = null;
+ HashSet? overlappingTables = null;
+ foreach (var property in index.Properties)
+ {
+ var tablesMappedToProperty = property.GetMappedStoreObjects(storeObject.StoreObjectType).ToList();
+ if (tablesMappedToProperty.Count == 0)
+ {
+ propertyNotMappedToAnyTable = property;
+ overlappingTables = null;
+
+ if (firstPropertyTables != null)
+ {
+ // Property is not mapped but we already found a property that is mapped.
+ break;
+ }
+
+ continue;
+ }
+
+ if (firstPropertyTables == null)
+ {
+ firstPropertyTables = (property.Name, tablesMappedToProperty);
+ }
+ else
+ {
+ lastPropertyTables = (property.Name, tablesMappedToProperty);
+ }
+
+ if (propertyNotMappedToAnyTable != null)
+ {
+ // Property is mapped but we already found a property that is not mapped.
+ overlappingTables = null;
+ break;
+ }
+
+ if (overlappingTables == null)
+ {
+ overlappingTables = new(tablesMappedToProperty);
+ }
+ else
+ {
+ overlappingTables.IntersectWith(tablesMappedToProperty);
+ if (overlappingTables.Count == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ if (overlappingTables == null)
+ {
+ if (firstPropertyTables == null)
+ {
+ logger.AllIndexPropertiesNotToMappedToAnyTable(
+ (IEntityType)index.DeclaringEntityType,
+ (IIndex)index);
+ }
+ else
+ {
+ logger.IndexPropertiesBothMappedAndNotMappedToTable(
+ (IEntityType)index.DeclaringEntityType,
+ (IIndex)index,
+ propertyNotMappedToAnyTable!.Name);
+ }
+ }
+ else if (overlappingTables.Count == 0)
+ {
+ Check.DebugAssert(firstPropertyTables != null, nameof(firstPropertyTables));
+ Check.DebugAssert(lastPropertyTables != null, nameof(lastPropertyTables));
+
+ logger.IndexPropertiesMappedToNonOverlappingTables(
+ (IEntityType)index.DeclaringEntityType,
+ (IIndex)index,
+ firstPropertyTables.Value.Item1,
+ firstPropertyTables.Value.Item2.Select(t => (t.Name, t.Schema)).ToList(),
+ lastPropertyTables.Value.Item1,
+ lastPropertyTables.Value.Item2.Select(t => (t.Name, t.Schema)).ToList());
+ }
+ }
+
+ return null;
+ }
+
+ var rootIndex = index;
+
+ // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
+ // Using a hashset is detrimental to the perf when there are no cycles
+ for (var i = 0; i < RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
+ {
+ IReadOnlyIndex? linkedIndex = null;
+ foreach (var otherIndex in rootIndex.DeclaringEntityType
+ .FindRowInternalForeignKeys(storeObject)
+ .SelectMany(fk => fk.PrincipalEntityType.GetIndexes()))
+ {
+ var otherColumnNames = otherIndex.Properties.GetColumnNames(storeObject);
+ if ((otherColumnNames != null)
+ && otherColumnNames.SequenceEqual(columnNames))
+ {
+ linkedIndex = otherIndex;
+ break;
+ }
+ }
+
+ if (linkedIndex == null)
+ {
+ break;
+ }
+
+ rootIndex = linkedIndex;
+ }
+
+ if (rootIndex != index)
+ {
+ return rootIndex.GetDatabaseName(storeObject);
+ }
+
+ var baseName = new StringBuilder()
+ .Append("IX_")
+ .Append(storeObject.Name)
+ .Append('_')
+ .AppendJoin(columnNames, "_")
+ .ToString();
+
+ return Uniquifier.Truncate(baseName, index.DeclaringEntityType.Model.GetMaxIdentifierLength());
+ }
}
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs
index a63c9829f10..a50faacc0d4 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
+
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
@@ -65,4 +67,157 @@ public static bool AreCompatible(
return true;
}
+
+ ///
+ /// 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? GetName(
+ this IReadOnlyKey key,
+ in StoreObjectIdentifier storeObject,
+ IDiagnosticsLogger? logger)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ var defaultName = key.GetDefaultName(storeObject, logger);
+ var declaringType = key.DeclaringEntityType;
+ var fragment = declaringType.FindMappingFragment(storeObject);
+ if (fragment != null)
+ {
+ return defaultName != null
+ ? (string?)key[RelationalAnnotationNames.Name] ?? defaultName
+ : null;
+ }
+
+ foreach (var containingType in declaringType.GetDerivedTypesInclusive())
+ {
+ if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject)
+ {
+ return defaultName != null
+ ? (string?)key[RelationalAnnotationNames.Name] ?? defaultName
+ : null;
+ }
+ }
+
+ return 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? GetDefaultName(
+ this IReadOnlyKey key,
+ in StoreObjectIdentifier storeObject,
+ IDiagnosticsLogger? logger)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ string? name;
+ if (key.IsPrimaryKey())
+ {
+ var rootKey = key;
+ // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
+ // Using a hashset is detrimental to the perf when there are no cycles
+ for (var i = 0; i < RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
+ {
+ var linkingFk = rootKey!.DeclaringEntityType.FindRowInternalForeignKeys(storeObject)
+ .FirstOrDefault();
+ if (linkingFk == null)
+ {
+ break;
+ }
+
+ rootKey = linkingFk.PrincipalEntityType.FindPrimaryKey();
+ }
+
+ if (rootKey != null
+ && rootKey != key)
+ {
+ return rootKey.GetName(storeObject);
+ }
+
+ name = "PK_" + storeObject.Name;
+ }
+ else
+ {
+ var columnNames = key.Properties.GetColumnNames(storeObject);
+ if (columnNames == null)
+ {
+ if (logger != null)
+ {
+ var table = storeObject;
+ if (key.DeclaringEntityType.GetMappingFragments(StoreObjectType.Table)
+ .Any(t => t.StoreObject != table && key.Properties.GetColumnNames(t.StoreObject) != null))
+ {
+ return null;
+ }
+
+ if (key.DeclaringEntityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy
+ && key.DeclaringEntityType.GetDerivedTypes()
+ .Select(e => StoreObjectIdentifier.Create(e, StoreObjectType.Table))
+ .Any(t => t != null && key.Properties.GetColumnNames(t.Value) != null))
+ {
+ return null;
+ }
+
+ logger.KeyUnmappedProperties((IKey)key);
+ }
+
+ return null;
+ }
+
+ var rootKey = key;
+
+ // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
+ // Using a hashset is detrimental to the perf when there are no cycles
+ for (var i = 0; i < RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
+ {
+ IReadOnlyKey? linkedKey = null;
+ foreach (var otherKey in rootKey.DeclaringEntityType
+ .FindRowInternalForeignKeys(storeObject)
+ .SelectMany(fk => fk.PrincipalEntityType.GetKeys()))
+ {
+ var otherColumnNames = otherKey.Properties.GetColumnNames(storeObject);
+ if ((otherColumnNames != null)
+ && otherColumnNames.SequenceEqual(columnNames))
+ {
+ linkedKey = otherKey;
+ break;
+ }
+ }
+
+ if (linkedKey == null)
+ {
+ break;
+ }
+
+ rootKey = linkedKey;
+ }
+
+ if (rootKey != key)
+ {
+ return rootKey.GetName(storeObject);
+ }
+
+ name = new StringBuilder()
+ .Append("AK_")
+ .Append(storeObject.Name)
+ .Append('_')
+ .AppendJoin(columnNames, "_")
+ .ToString();
+ }
+
+ return Uniquifier.Truncate(name, key.DeclaringEntityType.Model.GetMaxIdentifierLength());
+ }
}
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
index 52fdf3dc224..6bce5b2c861 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
@@ -273,12 +273,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp
databaseModel.DefaultTables.Add(mappedTableName, defaultTable);
}
- var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: !isTpc && mappedType == entityType)
- {
- // Table splitting is not supported for default mapping
- IsSharedTablePrincipal = true,
- IsSplitEntityTypePrincipal = true
- };
+ var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: !isTpc && mappedType == entityType);
foreach (var property in entityType.GetProperties())
{
@@ -338,8 +333,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp
private static void AddTables(RelationalModel databaseModel, IEntityType entityType)
{
- var tableName = entityType.GetTableName();
- if (tableName == null)
+ if (entityType.GetTableName() == null)
{
return;
}
@@ -350,8 +344,8 @@ private static void AddTables(RelationalModel databaseModel, IEntityType entityT
var tableMappings = new List();
entityType.SetRuntimeAnnotation(RelationalAnnotationNames.TableMappings, tableMappings);
- var isTpc = entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy;
- var isTph = entityType.FindDiscriminatorProperty() != null;
+ var mappingStrategy = entityType.GetMappingStrategy();
+ var isTpc = mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy;
while (mappedType != null)
{
var mappedTableName = mappedType.GetTableName();
@@ -368,75 +362,107 @@ private static void AddTables(RelationalModel databaseModel, IEntityType entityT
continue;
}
- if (!databaseModel.Tables.TryGetValue((mappedTableName, mappedSchema), out var table))
+ foreach (var fragment in mappedType.GetMappingFragments(StoreObjectType.Table))
{
- table = new Table(mappedTableName, mappedSchema, databaseModel);
- databaseModel.Tables.Add((mappedTableName, mappedSchema), table);
+ CreateTableMapping(
+ entityType,
+ mappedType,
+ fragment.StoreObject,
+ databaseModel,
+ tableMappings,
+ includesDerivedTypes: !isTpc && mappedType == entityType,
+ isSplitEntityTypePrincipal: false);
}
- var mappedTable = StoreObjectIdentifier.Table(mappedTableName, mappedSchema);
- var tableMapping = new TableMapping(entityType, table, includesDerivedTypes: !isTpc && mappedType == entityType)
- {
- IsSplitEntityTypePrincipal = true
- };
- foreach (var property in mappedType.GetProperties())
+ CreateTableMapping(
+ entityType,
+ mappedType,
+ StoreObjectIdentifier.Table(mappedTableName, mappedSchema),
+ databaseModel,
+ tableMappings,
+ includesDerivedTypes: !isTpc && mappedType == entityType,
+ isSplitEntityTypePrincipal: mappedType.GetMappingFragments(StoreObjectType.Table).Any() ? true : null);
+
+ if (isTpc || mappingStrategy == RelationalAnnotationNames.TphMappingStrategy)
{
- var columnName = property.GetColumnName(mappedTable);
- if (columnName == null)
- {
- continue;
- }
+ break;
+ }
- var column = (Column?)table.FindColumn(columnName);
- if (column == null)
- {
- column = new(columnName, property.GetColumnType(mappedTable), table)
- {
- IsNullable = property.IsColumnNullable(mappedTable)
- };
- table.Columns.Add(columnName, column);
- }
- else if (!property.IsColumnNullable(mappedTable))
- {
- column.IsNullable = false;
- }
+ mappedType = mappedType.BaseType;
+ }
- var columnMapping = new ColumnMapping(property, column, tableMapping);
- tableMapping.AddColumnMapping(columnMapping);
- column.AddPropertyMapping(columnMapping);
+ tableMappings.Reverse();
+ }
- if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableColumnMappings)
- is not SortedSet columnMappings)
- {
- columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance);
- property.AddRuntimeAnnotation(RelationalAnnotationNames.TableColumnMappings, columnMappings);
- }
+ private static TableMapping CreateTableMapping(
+ IEntityType entityType,
+ IEntityType mappedType,
+ StoreObjectIdentifier mappedTable,
+ RelationalModel databaseModel,
+ List tableMappings,
+ bool includesDerivedTypes,
+ bool? isSplitEntityTypePrincipal = null)
+ {
+ if (!databaseModel.Tables.TryGetValue((mappedTable.Name, mappedTable.Schema), out var table))
+ {
+ table = new Table(mappedTable.Name, mappedTable.Schema, databaseModel);
+ databaseModel.Tables.Add((mappedTable.Name, mappedTable.Schema), table);
+ }
- columnMappings.Add(columnMapping);
+ var tableMapping = new TableMapping(entityType, table, includesDerivedTypes)
+ {
+ IsSplitEntityTypePrincipal = isSplitEntityTypePrincipal
+ };
+
+ foreach (var property in mappedType.GetProperties())
+ {
+ var columnName = property.GetColumnName(mappedTable);
+ if (columnName == null)
+ {
+ continue;
}
- if (((ITableMappingBase)tableMapping).ColumnMappings.Any()
- || tableMappings.Count == 0)
+ var column = (Column?)table.FindColumn(columnName);
+ if (column == null)
{
- tableMappings.Add(tableMapping);
- table.EntityTypeMappings.Add(tableMapping);
+ column = new(columnName, property.GetColumnType(mappedTable), table)
+ {
+ IsNullable = property.IsColumnNullable(mappedTable)
+ };
+ table.Columns.Add(columnName, column);
+ }
+ else if (!property.IsColumnNullable(mappedTable))
+ {
+ column.IsNullable = false;
}
- if (isTpc || isTph)
+ var columnMapping = new ColumnMapping(property, column, tableMapping);
+ tableMapping.AddColumnMapping(columnMapping);
+ column.AddPropertyMapping(columnMapping);
+
+ if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableColumnMappings)
+ is not SortedSet columnMappings)
{
- break;
+ columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance);
+ property.AddRuntimeAnnotation(RelationalAnnotationNames.TableColumnMappings, columnMappings);
}
- mappedType = mappedType.BaseType;
+ columnMappings.Add(columnMapping);
}
- tableMappings.Reverse();
+ if (((ITableMappingBase)tableMapping).ColumnMappings.Any()
+ || tableMappings.Count == 0)
+ {
+ tableMappings.Add(tableMapping);
+ table.EntityTypeMappings.Add(tableMapping);
+ }
+
+ return tableMapping;
}
private static void AddViews(RelationalModel databaseModel, IEntityType entityType)
{
- var viewName = entityType.GetViewName();
- if (viewName == null)
+ if (entityType.GetViewName() == null)
{
return;
}
@@ -447,8 +473,8 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy
var viewMappings = new List();
entityType.SetRuntimeAnnotation(RelationalAnnotationNames.ViewMappings, viewMappings);
- var isTpc = entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy;
- var isTph = entityType.FindDiscriminatorProperty() != null;
+ var mappingStrategy = entityType.GetMappingStrategy();
+ var isTpc = mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy;
while (mappedType != null)
{
var mappedViewName = mappedType.GetViewName();
@@ -465,69 +491,99 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy
continue;
}
- if (!databaseModel.Views.TryGetValue((mappedViewName, mappedSchema), out var view))
+ foreach (var fragment in mappedType.GetMappingFragments(StoreObjectType.View))
{
- view = new View(mappedViewName, mappedSchema, databaseModel);
- databaseModel.Views.Add((mappedViewName, mappedSchema), view);
+ CreateViewMapping(
+ entityType,
+ mappedType,
+ fragment.StoreObject,
+ databaseModel,
+ viewMappings,
+ includesDerivedTypes: !isTpc && mappedType == entityType,
+ isSplitEntityTypePrincipal: false);
}
- var mappedView = StoreObjectIdentifier.View(mappedViewName, mappedSchema);
- var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes: !isTpc && mappedType == entityType)
- {
- IsSplitEntityTypePrincipal = true
- };
- foreach (var property in mappedType.GetProperties())
+ CreateViewMapping(
+ entityType,
+ mappedType,
+ StoreObjectIdentifier.View(mappedViewName, mappedSchema),
+ databaseModel,
+ viewMappings,
+ includesDerivedTypes: !isTpc && mappedType == entityType,
+ isSplitEntityTypePrincipal: mappedType.GetMappingFragments(StoreObjectType.View).Any() ? true : null);
+
+ if (isTpc || mappingStrategy == RelationalAnnotationNames.TphMappingStrategy)
{
- var columnName = property.GetColumnName(mappedView);
- if (columnName == null)
- {
- continue;
- }
+ break;
+ }
- var column = (ViewColumn?)view.FindColumn(columnName);
- if (column == null)
- {
- column = new ViewColumn(columnName, property.GetColumnType(mappedView), view)
- {
- IsNullable = property.IsColumnNullable(mappedView)
- };
- view.Columns.Add(columnName, column);
- }
- else if (!property.IsColumnNullable(mappedView))
- {
- column.IsNullable = false;
- }
+ mappedType = mappedType.BaseType;
+ }
- var columnMapping = new ViewColumnMapping(property, column, viewMapping);
- viewMapping.AddColumnMapping(columnMapping);
- column.AddPropertyMapping(columnMapping);
+ viewMappings.Reverse();
+ }
- if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewColumnMappings)
- is not SortedSet columnMappings)
- {
- columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance);
- property.AddRuntimeAnnotation(RelationalAnnotationNames.ViewColumnMappings, columnMappings);
- }
+ private static void CreateViewMapping(
+ IEntityType entityType,
+ IEntityType mappedType,
+ StoreObjectIdentifier mappedView,
+ RelationalModel databaseModel,
+ List viewMappings,
+ bool includesDerivedTypes,
+ bool? isSplitEntityTypePrincipal = null)
+ {
+ if (!databaseModel.Views.TryGetValue((mappedView.Name, mappedView.Schema), out var view))
+ {
+ view = new View(mappedView.Name, mappedView.Schema, databaseModel);
+ databaseModel.Views.Add((mappedView.Name, mappedView.Schema), view);
+ }
- columnMappings.Add(columnMapping);
+ var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes)
+ {
+ IsSplitEntityTypePrincipal = isSplitEntityTypePrincipal
+ };
+ foreach (var property in mappedType.GetProperties())
+ {
+ var columnName = property.GetColumnName(mappedView);
+ if (columnName == null)
+ {
+ continue;
}
- if (((ITableMappingBase)viewMapping).ColumnMappings.Any()
- || viewMappings.Count == 0)
+ var column = (ViewColumn?)view.FindColumn(columnName);
+ if (column == null)
+ {
+ column = new ViewColumn(columnName, property.GetColumnType(mappedView), view)
+ {
+ IsNullable = property.IsColumnNullable(mappedView)
+ };
+ view.Columns.Add(columnName, column);
+ }
+ else if (!property.IsColumnNullable(mappedView))
{
- viewMappings.Add(viewMapping);
- view.EntityTypeMappings.Add(viewMapping);
+ column.IsNullable = false;
}
- if (isTpc || isTph)
+ var columnMapping = new ViewColumnMapping(property, column, viewMapping);
+ viewMapping.AddColumnMapping(columnMapping);
+ column.AddPropertyMapping(columnMapping);
+
+ if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewColumnMappings)
+ is not SortedSet columnMappings)
{
- break;
+ columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance);
+ property.AddRuntimeAnnotation(RelationalAnnotationNames.ViewColumnMappings, columnMappings);
}
- mappedType = mappedType.BaseType;
+ columnMappings.Add(columnMapping);
}
- viewMappings.Reverse();
+ if (((ITableMappingBase)viewMapping).ColumnMappings.Any()
+ || viewMappings.Count == 0)
+ {
+ viewMappings.Add(viewMapping);
+ view.EntityTypeMappings.Add(viewMapping);
+ }
}
private static void AddSqlQueries(RelationalModel databaseModel, IEntityType entityType)
@@ -576,9 +632,7 @@ private static void AddSqlQueries(RelationalModel databaseModel, IEntityType ent
var queryMapping = new SqlQueryMapping(entityType, sqlQuery, includesDerivedTypes: true)
{
- IsDefaultSqlQueryMapping = true,
- IsSharedTablePrincipal = true,
- IsSplitEntityTypePrincipal = true
+ IsDefaultSqlQueryMapping = true
};
foreach (var property in mappedType.GetProperties())
@@ -725,10 +779,7 @@ private static FunctionMapping CreateFunctionMapping(
var mappedFunction = StoreObjectIdentifier.DbFunction(dbFunction.Name);
var functionMapping = new FunctionMapping(entityType, storeFunction, dbFunction, includesDerivedTypes: true)
{
- IsDefaultFunctionMapping = @default,
- // See Issue #19970
- IsSharedTablePrincipal = true,
- IsSplitEntityTypePrincipal = true
+ IsDefaultFunctionMapping = @default
};
foreach (var property in mappedType.GetProperties())
@@ -947,6 +998,11 @@ private static void PopulateRowInternalForeignKeys(TableBase tab
var mappedEntityTypes = new HashSet();
foreach (TableMappingBase entityTypeMapping in table.EntityTypeMappings)
{
+ if (table.EntityTypeMappings.Count > 1)
+ {
+ entityTypeMapping.IsSharedTablePrincipal = false;
+ }
+
var entityType = entityTypeMapping.EntityType;
mappedEntityTypes.Add(entityType);
var primaryKey = entityType.FindPrimaryKey();
@@ -1015,10 +1071,13 @@ private static void PopulateRowInternalForeignKeys(TableBase tab
mainMapping is not null,
$"{nameof(mainMapping)} is neither a {nameof(TableMapping)} nor a {nameof(ViewMapping)}");
- // Re-add the mapping to update the order
- mainMapping.Table.EntityTypeMappings.Remove(mainMapping);
- mainMapping.IsSharedTablePrincipal = true;
- mainMapping.Table.EntityTypeMappings.Add(mainMapping);
+ if (table.EntityTypeMappings.Count > 1)
+ {
+ // Re-add the mapping to update the order
+ mainMapping.Table.EntityTypeMappings.Remove(mainMapping);
+ mainMapping.IsSharedTablePrincipal = true;
+ mainMapping.Table.EntityTypeMappings.Add(mainMapping);
+ }
if (referencingInternalForeignKeyMap != null)
{
@@ -1065,7 +1124,7 @@ private static void PopulateRowInternalForeignKeys(TableBase tab
}
else
{
- table.OptionalEntityTypes = table.EntityTypeMappings.ToDictionary(etm => etm.EntityType, et => false);
+ table.OptionalEntityTypes = table.EntityTypeMappings.ToDictionary(etm => etm.EntityType, _ => false);
}
}
@@ -1083,20 +1142,8 @@ private static void PopulateForeignKeyConstraints(Table table)
var entityType = entityTypeMapping.EntityType;
foreach (var foreignKey in entityType.GetForeignKeys())
{
- var firstPrincipalMapping = true;
foreach (var principalMapping in foreignKey.PrincipalEntityType.GetTableMappings().Reverse())
{
- if (firstPrincipalMapping
- && !principalMapping.IncludesDerivedTypes
- && foreignKey.PrincipalEntityType.GetDirectlyDerivedTypes().Any(e => e.GetTableMappings().Any()))
- {
- // Derived principal entity types are mapped to different tables, so the constraint is not enforceable
- // TODO: Allow this to be overriden #15854
- break;
- }
-
- firstPrincipalMapping = false;
-
var principalTable = (Table)principalMapping.Table;
var principalStoreObject = StoreObjectIdentifier.Table(principalTable.Name, principalTable.Schema);
var name = foreignKey.GetConstraintName(storeObject, principalStoreObject);
@@ -1138,6 +1185,7 @@ private static void PopulateForeignKeyConstraints(Table table)
if (principalColumns == null)
{
+ Check.DebugAssert(false, "Should not get here if name is not null");
continue;
}
@@ -1155,14 +1203,10 @@ private static void PopulateForeignKeyConstraints(Table table)
}
}
- if (columns == null)
- {
- break;
- }
-
- if (columns.SequenceEqual(principalColumns))
+ if (columns == null
+ || columns.SequenceEqual(principalColumns))
{
- // Principal and dependent properties are mapped to the same columns so the constraint is redundant
+ Check.DebugAssert(false, "Should not get here if name is not null");
break;
}
@@ -1171,8 +1215,7 @@ private static void PopulateForeignKeyConstraints(Table table)
&& entityType.FindPrimaryKey() is IKey primaryKey
&& foreignKey.Properties.SequenceEqual(primaryKey.Properties))
{
- // The identifying FK constraint is needed to be created only on the table that corresponds
- // to the declaring entity type
+ Check.DebugAssert(false, "Should not get here if name is not null");
break;
}
diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs
index 619a422333d..6cde87ddd45 100644
--- a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs
+++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs
@@ -80,10 +80,10 @@ public virtual bool AddColumnMapping(TColumnMapping columnMapping)
public virtual bool IncludesDerivedTypes { get; }
///
- public virtual bool IsSharedTablePrincipal { get; set; }
+ public virtual bool? IsSharedTablePrincipal { get; set; }
///
- public virtual bool IsSplitEntityTypePrincipal { get; set; }
+ public virtual bool? IsSplitEntityTypePrincipal { get; set; }
IEnumerable ITableMappingBase.ColumnMappings
{
diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs
index a445e6600e2..4a9f4ebefd1 100644
--- a/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs
+++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs
@@ -47,22 +47,53 @@ public int Compare(ITableMappingBase? x, ITableMappingBase? y)
return 1;
}
- var result = y.IsSharedTablePrincipal.CompareTo(x.IsSharedTablePrincipal);
- if (result != 0)
+ var result = 0;
+ if (y.IsSharedTablePrincipal == null)
{
- return result;
+ if (x.IsSharedTablePrincipal != null)
+ {
+ return 1;
+ }
}
-
+ else
+ {
+ if (x.IsSharedTablePrincipal == null)
+ {
+ return -1;
+ }
+
+ result = y.IsSharedTablePrincipal.Value.CompareTo(x.IsSharedTablePrincipal.Value);
+ if (result != 0)
+ {
+ return result;
+ }
+ }
+
result = y.IncludesDerivedTypes.CompareTo(x.IncludesDerivedTypes);
if (result != 0)
{
return result;
}
- result = y.IsSplitEntityTypePrincipal.CompareTo(x.IsSplitEntityTypePrincipal);
- if (result != 0)
+ if (y.IsSplitEntityTypePrincipal == null)
{
- return result;
+ if (x.IsSplitEntityTypePrincipal != null)
+ {
+ return 1;
+ }
+ }
+ else
+ {
+ if (x.IsSplitEntityTypePrincipal == null)
+ {
+ return -1;
+ }
+
+ result = y.IsSplitEntityTypePrincipal.Value.CompareTo(x.IsSplitEntityTypePrincipal.Value);
+ if (result != 0)
+ {
+ return result;
+ }
}
result = EntityTypeFullNameComparer.Instance.Compare(x.EntityType, y.EntityType);
diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
index dc97a4c75b4..1ef190f855d 100644
--- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
+++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
@@ -2396,7 +2396,7 @@ protected virtual IEnumerable GetSchemas(IRelationalModel model)
=> (property.FindRelationalTypeMapping() ?? typeMapping)?.Converter;
private static IEntityType GetMainType(ITable table)
- => table.EntityTypeMappings.First(t => t.IsSharedTablePrincipal).EntityType;
+ => table.EntityTypeMappings.First(t => t.IsSharedTablePrincipal ?? true).EntityType;
private static object?[,] ToMultidimensionalArray(IReadOnlyList
protected override void ValidateSharedTableCompatibility(
IReadOnlyList mappedTypes,
- string tableName,
- string? schema,
+ in StoreObjectIdentifier storeObject,
IDiagnosticsLogger logger)
{
var firstMappedType = mappedTypes[0];
@@ -326,7 +324,7 @@ protected override void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
SqlServerStrings.IncompatibleTableMemoryOptimizedMismatch(
- tableName, firstMappedType.DisplayName(), otherMappedType.DisplayName(),
+ storeObject.DisplayName(), firstMappedType.DisplayName(), otherMappedType.DisplayName(),
isMemoryOptimized ? firstMappedType.DisplayName() : otherMappedType.DisplayName(),
!isMemoryOptimized ? firstMappedType.DisplayName() : otherMappedType.DisplayName()));
}
@@ -354,10 +352,9 @@ protected override void ValidateSharedTableCompatibility(
var periodStartProperty = mappedType.GetProperty(periodStartPropertyName!);
var periodEndProperty = mappedType.GetProperty(periodEndPropertyName!);
-
- var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, mappedType.GetSchema());
- var periodStartColumnName = periodStartProperty.GetColumnName(storeObjectIdentifier);
- var periodEndColumnName = periodEndProperty.GetColumnName(storeObjectIdentifier);
+
+ var periodStartColumnName = periodStartProperty.GetColumnName(storeObject);
+ var periodEndColumnName = periodEndProperty.GetColumnName(storeObject);
if (expectedPeriodStartColumnName == null)
{
@@ -391,7 +388,7 @@ protected override void ValidateSharedTableCompatibility(
}
}
- base.ValidateSharedTableCompatibility(mappedTypes, tableName, schema, logger);
+ base.ValidateSharedTableCompatibility(mappedTypes, storeObject, logger);
}
///
diff --git a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs
index e8c2670cd64..fd9f2604c1f 100644
--- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs
+++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs
@@ -219,7 +219,8 @@ public override IEnumerable For(IColumn column, bool designTime)
var table = StoreObjectIdentifier.Table(column.Table.Name, column.Table.Schema);
var identityProperty = column.PropertyMappings.Where(
- m => m.TableMapping.IsSharedTablePrincipal && m.TableMapping.EntityType == m.Property.DeclaringEntityType)
+ m => (m.TableMapping.IsSharedTablePrincipal ?? true)
+ && m.TableMapping.EntityType == m.Property.DeclaringEntityType)
.Select(m => m.Property)
.FirstOrDefault(
p => p.GetValueGenerationStrategy(table)
diff --git a/src/EFCore/Diagnostics/KeyEventData.cs b/src/EFCore/Diagnostics/KeyEventData.cs
new file mode 100644
index 00000000000..0755183e20c
--- /dev/null
+++ b/src/EFCore/Diagnostics/KeyEventData.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Diagnostics;
+
+///
+/// A event payload class for events that have a key.
+///
+///
+/// See Logging, events, and diagnostics for more information and examples.
+///
+public class KeyEventData : EventData
+{
+ ///
+ /// Constructs the event payload.
+ ///
+ /// The event definition.
+ /// A delegate that generates a log message for this event.
+ /// The key.
+ public KeyEventData(
+ EventDefinitionBase eventDefinition,
+ Func messageGenerator,
+ IReadOnlyKey key)
+ : base(eventDefinition, messageGenerator)
+ {
+ Key = key;
+ }
+
+ ///
+ /// The foreign key.
+ ///
+ public virtual IReadOnlyKey Key { get; }
+}
diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs
index 69e622a108b..7f6554003cb 100644
--- a/src/EFCore/Infrastructure/ModelValidator.cs
+++ b/src/EFCore/Infrastructure/ModelValidator.cs
@@ -684,6 +684,8 @@ protected virtual void ValidateOwnership(
foreach (var referencingFk in entityType.GetReferencingForeignKeys().Where(
fk => !fk.IsOwnership
+ && (fk.PrincipalEntityType != fk.DeclaringEntityType
+ || !fk.Properties.SequenceEqual(entityType.FindPrimaryKey()!.Properties))
&& !Contains(fk.DeclaringEntityType.FindOwnership(), fk)))
{
throw new InvalidOperationException(
@@ -738,8 +740,7 @@ protected virtual void ValidateForeignKeys(
{
foreach (var declaredForeignKey in entityType.GetDeclaredForeignKeys())
{
- if (declaredForeignKey.PrincipalEntityType == declaredForeignKey.DeclaringEntityType
- && declaredForeignKey.PrincipalKey.Properties.SequenceEqual(declaredForeignKey.Properties))
+ if (IsRedundant(declaredForeignKey))
{
logger.RedundantForeignKeyWarning(declaredForeignKey);
}
@@ -783,6 +784,15 @@ static bool ContainedInForeignKeyForAllConcreteTypes(IEntityType entityType, IPr
.Any(fk => fk.Properties.Contains(property)));
}
+ ///
+ /// Returns a value indicating whether the given foreign key is redundant.
+ ///
+ /// A foreign key.
+ /// A value indicating whether the given foreign key is redundant.
+ protected virtual bool IsRedundant(IForeignKey foreignKey)
+ => foreignKey.PrincipalEntityType == foreignKey.DeclaringEntityType
+ && foreignKey.PrincipalKey.Properties.SequenceEqual(foreignKey.Properties);
+
///
/// Validates the mapping/configuration of properties mapped to fields in the model.
///
diff --git a/src/EFCore/Metadata/IReadOnlyIndex.cs b/src/EFCore/Metadata/IReadOnlyIndex.cs
index f7ab17a9a4a..f061bc98189 100644
--- a/src/EFCore/Metadata/IReadOnlyIndex.cs
+++ b/src/EFCore/Metadata/IReadOnlyIndex.cs
@@ -82,7 +82,10 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
? p.DeclaringEntityType.DisplayName(omitSharedType: true) + "." + p.Name
: p.Name));
- builder.Append(" " + (Name ?? ""));
+ if (Name != null)
+ {
+ builder.Append(" " + Name);
+ }
if (IsUnique)
{
diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs
index 3679a32197b..4bda85d0126 100644
--- a/src/EFCore/Metadata/Internal/Property.cs
+++ b/src/EFCore/Metadata/Internal/Property.cs
@@ -1170,7 +1170,7 @@ public static bool AreCompatible(IReadOnlyList properties, EntityType
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public override string ToString()
- => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+ => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -1180,8 +1180,8 @@ public override string ToString()
///
public virtual DebugView DebugView
=> new(
- () => this.ToDebugString(),
- () => this.ToDebugString(MetadataDebugStringOptions.LongDefault));
+ () => ((IReadOnlyProperty)this).ToDebugString(),
+ () => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs
index f0714f44d7f..c14d32bfad4 100644
--- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs
+++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs
@@ -151,16 +151,4 @@ public static bool RequiresOriginalValue(this IReadOnlyProperty property)
|| property.IsKey()
|| property.IsForeignKey()
|| property.IsUniqueIndex();
-
- ///
- /// 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 ToDebugString(
- this Property property,
- MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault,
- int indent = 0)
- => ((IReadOnlyProperty)property).ToDebugString(options, indent);
}
diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs
index 29fdfb3520a..0425cd294e4 100644
--- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs
@@ -16,7 +16,7 @@ protected CosmosTestHelpers()
public override IServiceCollection AddProviderServices(IServiceCollection services)
=> services.AddEntityFrameworkCosmos();
- public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
+ public override DbContextOptionsBuilder UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
TestEnvironment.DefaultConnection,
TestEnvironment.AuthToken,
diff --git a/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs b/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs
index 706e2152c50..deb8a117c46 100644
--- a/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs
+++ b/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs
@@ -10,7 +10,7 @@ public class CosmosModelValidatorTest : ModelValidatorTestBase
[ConditionalFact]
public virtual void Passes_on_valid_model()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
Validate(modelBuilder);
@@ -19,7 +19,7 @@ public virtual void Passes_on_valid_model()
[ConditionalFact]
public virtual void Passes_on_valid_keyless_entity_type()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasPartitionKey(c => c.PartitionId).HasNoKey();
var model = Validate(modelBuilder);
@@ -87,7 +87,7 @@ public virtual void Detects_non_string_id_property()
[ConditionalFact]
public virtual void Passes_on_valid_partition_keys()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders").HasPartitionKey(c => c.PartitionId)
.HasAnalyticalStoreTimeToLive(-1)
.HasDefaultTimeToLive(100)
@@ -101,7 +101,7 @@ public virtual void Passes_on_valid_partition_keys()
[ConditionalFact]
public virtual void Passes_PK_partition_key()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity(
b =>
{
@@ -137,7 +137,7 @@ public virtual void Detects_non_key_partition_key_property()
[ConditionalFact]
public virtual void Detects_missing_partition_key_property()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().HasPartitionKey("PartitionKey");
@@ -147,7 +147,7 @@ public virtual void Detects_missing_partition_key_property()
[ConditionalFact]
public virtual void Detects_missing_partition_key_on_first_type()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders");
modelBuilder.Entity().ToContainer("Orders").HasPartitionKey(c => c.PartitionId);
@@ -157,7 +157,7 @@ public virtual void Detects_missing_partition_key_on_first_type()
[ConditionalFact]
public virtual void Detects_missing_partition_keys_one_last_type()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders").HasPartitionKey(c => c.PartitionId);
modelBuilder.Entity().ToContainer("Orders");
@@ -167,7 +167,7 @@ public virtual void Detects_missing_partition_keys_one_last_type()
[ConditionalFact]
public virtual void Detects_partition_keys_mapped_to_different_properties()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders").HasPartitionKey(c => c.PartitionId)
.Property(c => c.PartitionId).ToJsonProperty("pk");
modelBuilder.Entity().ToContainer("Orders").HasPartitionKey(c => c.PartitionId);
@@ -181,7 +181,7 @@ public virtual void Detects_partition_keys_mapped_to_different_properties()
[ConditionalFact]
public virtual void Detects_partition_key_of_different_type()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders").HasPartitionKey(c => c.PartitionId);
modelBuilder.Entity().ToContainer("Orders").HasPartitionKey(o => o.PartitionId)
.Property(c => c.PartitionId).HasConversion();
@@ -194,7 +194,7 @@ public virtual void Detects_partition_key_of_different_type()
[ConditionalFact]
public virtual void Detects_conflicting_analytical_ttl()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders")
.HasAnalyticalStoreTimeToLive(-1);
modelBuilder.Entity().ToContainer("Orders")
@@ -206,7 +206,7 @@ public virtual void Detects_conflicting_analytical_ttl()
[ConditionalFact]
public virtual void Detects_conflicting_default_ttl()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders")
.HasDefaultTimeToLive(100);
modelBuilder.Entity().ToContainer("Orders")
@@ -218,7 +218,7 @@ public virtual void Detects_conflicting_default_ttl()
[ConditionalFact]
public virtual void Detects_conflicting_throughput()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders")
.HasAutoscaleThroughput(200);
modelBuilder.Entity().ToContainer("Orders")
@@ -230,7 +230,7 @@ public virtual void Detects_conflicting_throughput()
[ConditionalFact]
public virtual void Detects_conflicting_throughput_type()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders")
.HasManualThroughput(200);
modelBuilder.Entity().ToContainer("Orders")
@@ -242,7 +242,7 @@ public virtual void Detects_conflicting_throughput_type()
[ConditionalFact]
public virtual void Detects_properties_mapped_to_same_property()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity(
ob =>
@@ -259,7 +259,7 @@ public virtual void Detects_properties_mapped_to_same_property()
[ConditionalFact]
public virtual void Detects_property_and_embedded_type_mapped_to_same_property()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity(
ob =>
@@ -276,7 +276,7 @@ public virtual void Detects_property_and_embedded_type_mapped_to_same_property()
[ConditionalFact]
public virtual void Detects_missing_discriminator()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders").HasNoDiscriminator();
modelBuilder.Entity().ToContainer("Orders");
@@ -286,7 +286,7 @@ public virtual void Detects_missing_discriminator()
[ConditionalFact]
public virtual void Detects_missing_discriminator_value()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders").HasDiscriminator().HasValue(null);
modelBuilder.Entity().ToContainer("Orders");
@@ -296,7 +296,7 @@ public virtual void Detects_missing_discriminator_value()
[ConditionalFact]
public virtual void Detects_duplicate_discriminator_values()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToContainer("Orders").HasDiscriminator().HasValue("type");
modelBuilder.Entity().ToContainer("Orders").HasDiscriminator().HasValue("type");
@@ -307,7 +307,7 @@ public virtual void Detects_duplicate_discriminator_values()
[ConditionalFact]
public virtual void Passes_on_valid_concurrency_token()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity()
.ToContainer("Orders")
.Property("_etag")
@@ -319,7 +319,7 @@ public virtual void Passes_on_valid_concurrency_token()
[ConditionalFact]
public virtual void Detects_invalid_concurrency_token()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity()
.ToContainer("Orders")
.Property("_not_etag")
@@ -331,7 +331,7 @@ public virtual void Detects_invalid_concurrency_token()
[ConditionalFact]
public virtual void Detects_nonString_concurrency_token()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity()
.ToContainer("Orders")
.Property("_etag")
diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
index 444a365cc41..3365dfeda36 100644
--- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
@@ -557,7 +557,7 @@ protected override void Down(MigrationBuilder migrationBuilder)
migrationCode,
ignoreLineEndingDifferences: true);
- var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(configure: c => c.RemoveAllConventions());
+ var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(configureModel: c => c.RemoveAllConventions());
modelBuilder.HasAnnotation("Some:EnumValue", RegexOptions.Multiline);
modelBuilder.HasAnnotation(RelationalAnnotationNames.DbFunctions, new SortedDictionary());
modelBuilder.Entity(
diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
index 0496f1e09b6..ef23e7ee37d 100644
--- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
@@ -916,6 +916,7 @@ partial void Initialize()
DependentBasebyteEntityType.CreateForeignKey1(dependentBasebyte, principalBase);
DependentBasebyteEntityType.CreateForeignKey2(dependentBasebyte, principalDerivedDependentBasebyte);
OwnedTypeEntityType.CreateForeignKey1(ownedType, principalBase);
+ OwnedTypeEntityType.CreateForeignKey2(ownedType, ownedType);
OwnedType0EntityType.CreateForeignKey1(ownedType0, principalDerivedDependentBasebyte);
PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateForeignKey1(principalBasePrincipalDerivedDependentBasebyte, principalDerivedDependentBasebyte);
PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateForeignKey2(principalBasePrincipalDerivedDependentBasebyte, principalBase);
@@ -1277,6 +1278,19 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt
return runtimeForeignKey;
}
+ public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType)
+ {
+ var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalBaseId"")!, declaringEntityType.FindProperty(""PrincipalBaseAlternateId"")! },
+ principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""PrincipalBaseId"")!, principalEntityType.FindProperty(""PrincipalBaseAlternateId"")! })!,
+ principalEntityType,
+ deleteBehavior: DeleteBehavior.ClientCascade,
+ unique: true,
+ required: true,
+ requiredDependent: true);
+
+ return runtimeForeignKey;
+ }
+
public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
{
var fragments = new StoreObjectDictionary();
@@ -3165,8 +3179,8 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
Assert.Null(dataEntity.FindPrimaryKey());
var dataEntityFunctionMapping = dataEntity.GetFunctionMappings().Single(m => m.IsDefaultFunctionMapping);
Assert.True(dataEntityFunctionMapping.IncludesDerivedTypes);
- Assert.True(dataEntityFunctionMapping.IsSharedTablePrincipal);
- Assert.True(dataEntityFunctionMapping.IsSplitEntityTypePrincipal);
+ Assert.Null(dataEntityFunctionMapping.IsSharedTablePrincipal);
+ Assert.Null(dataEntityFunctionMapping.IsSplitEntityTypePrincipal);
Assert.Same(getDataParameterless, dataEntityFunctionMapping.DbFunction);
var getDataStoreFunction = dataEntityFunctionMapping.StoreFunction;
@@ -3175,8 +3189,8 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
var dataEntityOtherFunctionMapping = dataEntity.GetFunctionMappings().Single(m => !m.IsDefaultFunctionMapping);
Assert.True(dataEntityOtherFunctionMapping.IncludesDerivedTypes);
- Assert.True(dataEntityOtherFunctionMapping.IsSharedTablePrincipal);
- Assert.True(dataEntityOtherFunctionMapping.IsSplitEntityTypePrincipal);
+ Assert.Null(dataEntityOtherFunctionMapping.IsSharedTablePrincipal);
+ Assert.Null(dataEntityOtherFunctionMapping.IsSplitEntityTypePrincipal);
Assert.Same(getData, dataEntityOtherFunctionMapping.DbFunction);
var getDataOtherStoreFunction = dataEntityOtherFunctionMapping.StoreFunction;
@@ -3204,8 +3218,8 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
Assert.Null(objectEntity.FindPrimaryKey());
var objectEntityFunctionMapping = objectEntity.GetFunctionMappings().Single(m => m.IsDefaultFunctionMapping);
Assert.True(objectEntityFunctionMapping.IncludesDerivedTypes);
- Assert.True(objectEntityFunctionMapping.IsSharedTablePrincipal);
- Assert.True(objectEntityFunctionMapping.IsSplitEntityTypePrincipal);
+ Assert.Null(objectEntityFunctionMapping.IsSharedTablePrincipal);
+ Assert.Null(objectEntityFunctionMapping.IsSplitEntityTypePrincipal);
Assert.Same(getBlobs, objectEntityFunctionMapping.DbFunction);
});
diff --git a/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs b/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs
index 28d1844302b..a36b1eab231 100644
--- a/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs
+++ b/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs
@@ -16,7 +16,7 @@ protected DesignTestHelpers()
public override IServiceCollection AddProviderServices(IServiceCollection services)
=> FakeRelationalOptionsExtension.AddEntityFrameworkRelationalDatabase(services);
- public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
+ public override DbContextOptionsBuilder UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseFakeRelational();
public override LoggingDefinitions LoggingDefinitions { get; } = new TestRelationalLoggingDefinitions();
diff --git a/test/EFCore.InMemory.FunctionalTests/TestUtilities/InMemoryTestHelpers.cs b/test/EFCore.InMemory.FunctionalTests/TestUtilities/InMemoryTestHelpers.cs
index 278b4a61983..1c2b460be11 100644
--- a/test/EFCore.InMemory.FunctionalTests/TestUtilities/InMemoryTestHelpers.cs
+++ b/test/EFCore.InMemory.FunctionalTests/TestUtilities/InMemoryTestHelpers.cs
@@ -16,7 +16,7 @@ protected InMemoryTestHelpers()
public override IServiceCollection AddProviderServices(IServiceCollection services)
=> services.AddEntityFrameworkInMemoryDatabase();
- public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
+ public override DbContextOptionsBuilder UseProviderOptions(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase(nameof(InMemoryTestHelpers));
public override LoggingDefinitions LoggingDefinitions { get; } = new InMemoryLoggingDefinitions();
diff --git a/test/EFCore.InMemory.Tests/Infrastructure/InMemoryModelValidatorTest.cs b/test/EFCore.InMemory.Tests/Infrastructure/InMemoryModelValidatorTest.cs
index a4afdbdf797..6399817a087 100644
--- a/test/EFCore.InMemory.Tests/Infrastructure/InMemoryModelValidatorTest.cs
+++ b/test/EFCore.InMemory.Tests/Infrastructure/InMemoryModelValidatorTest.cs
@@ -8,7 +8,7 @@ public class InMemoryModelValidatorTest : ModelValidatorTestBase
[ConditionalFact]
public virtual void Detects_ToQuery_on_derived_keyless_types()
{
- var modelBuilder = base.CreateConventionalModelBuilder();
+ var modelBuilder = base.CreateConventionModelBuilder();
var context = new DbContext(new DbContextOptions());
modelBuilder.Entity().HasNoKey().ToInMemoryQuery(() => context.Set());
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index c3fe92aeb28..99afb33690f 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -13,7 +13,7 @@ public partial class RelationalModelValidatorTest : ModelValidatorTest
{
public override void Detects_key_property_which_cannot_be_compared()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity(
eb =>
@@ -29,7 +29,7 @@ public override void Detects_key_property_which_cannot_be_compared()
public override void Detects_unique_index_property_which_cannot_be_compared()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity(
eb =>
@@ -45,7 +45,7 @@ public override void Detects_unique_index_property_which_cannot_be_compared()
public override void Ignores_normal_property_which_cannot_be_compared()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity(
eb =>
@@ -331,7 +331,7 @@ public virtual void Passes_for_duplicate_table_names_for_inherited_entities()
[ConditionalFact]
public virtual void Detects_incompatible_primary_keys_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().HasKey(a => a.Id).HasName("Key");
@@ -347,7 +347,7 @@ public virtual void Detects_incompatible_primary_keys_with_shared_table()
[ConditionalFact]
public virtual void Detects_incompatible_comments_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table").HasComment("My comment");
@@ -362,7 +362,7 @@ public virtual void Detects_incompatible_comments_with_shared_table()
[ConditionalFact]
public virtual void Passes_on_null_comments()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table").HasComment("My comment");
@@ -374,7 +374,7 @@ public virtual void Passes_on_null_comments()
[ConditionalFact]
public virtual void Detects_incompatible_primary_key_columns_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.Id).ValueGeneratedNever().HasColumnName("Key");
@@ -390,7 +390,7 @@ public virtual void Detects_incompatible_primary_key_columns_with_shared_table()
[ConditionalFact]
public virtual void Passes_on_not_configured_shared_columns_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0));
@@ -405,7 +405,7 @@ public virtual void Passes_on_not_configured_shared_columns_with_shared_table()
[ConditionalFact]
public virtual void Throws_on_not_configured_shared_columns_with_shared_table_with_dependents()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0));
@@ -419,7 +419,7 @@ public virtual void Throws_on_not_configured_shared_columns_with_shared_table_wi
[ConditionalFact]
public virtual void Warns_on_not_configured_shared_columns_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().OwnsOne(e => e.Owned);
@@ -431,7 +431,7 @@ public virtual void Warns_on_not_configured_shared_columns_with_shared_table()
[ConditionalFact]
public virtual void Detects_incompatible_shared_columns_in_shared_table_with_different_data_types()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt");
@@ -448,7 +448,7 @@ public virtual void Detects_incompatible_shared_columns_in_shared_table_with_dif
[ConditionalFact]
public virtual void Detects_incompatible_shared_columns_in_shared_table_with_different_provider_types()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt").HasConversion();
@@ -465,7 +465,7 @@ public virtual void Detects_incompatible_shared_columns_in_shared_table_with_dif
[ConditionalFact]
public virtual void Detects_incompatible_shared_check_constraints_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().HasCheckConstraint("SomeCK", "Id > 0", c => c.HasName("CK_Table_SomeCK"));
@@ -482,7 +482,7 @@ public virtual void Detects_incompatible_shared_check_constraints_with_shared_ta
[ConditionalFact]
public virtual void Passes_for_incompatible_uniquified_check_constraints_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0");
@@ -499,7 +499,7 @@ public virtual void Passes_for_incompatible_uniquified_check_constraints_with_sh
[ConditionalFact]
public virtual void Passes_for_compatible_shared_check_constraints_with_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0");
@@ -516,7 +516,7 @@ public virtual void Passes_for_compatible_shared_check_constraints_with_shared_t
[ConditionalFact]
public virtual void Detects_multiple_shared_table_roots()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table");
@@ -532,7 +532,7 @@ public virtual void Detects_multiple_shared_table_roots()
[ConditionalFact]
public virtual void Detects_shared_table_root_cycle()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table");
@@ -547,7 +547,7 @@ public virtual void Detects_shared_table_root_cycle()
[ConditionalFact]
public virtual void Passes_for_compatible_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
@@ -571,7 +571,7 @@ public virtual void Passes_for_compatible_shared_table()
[ConditionalFact]
public virtual void Passes_for_compatible_excluded_shared_table_inverted()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne().HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table", t => t.ExcludeFromMigrations());
@@ -583,7 +583,7 @@ public virtual void Passes_for_compatible_excluded_shared_table_inverted()
[ConditionalFact]
public virtual void Passes_for_compatible_excluded_shared_table_owned()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().OwnsOne(b => b.A);
modelBuilder.Entity().ToTable("Table", t => t.ExcludeFromMigrations());
@@ -598,7 +598,7 @@ public virtual void Passes_for_compatible_excluded_shared_table_owned()
[ConditionalFact]
public virtual void Passes_for_compatible_excluded_table_derived()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToTable("Table", t => t.ExcludeFromMigrations());
modelBuilder.Entity();
@@ -613,7 +613,7 @@ public virtual void Passes_for_compatible_excluded_table_derived()
[ConditionalFact]
public virtual void Detect_partially_excluded_shared_table()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().HasOne().WithOne().HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table", t => t.ExcludeFromMigrations());
@@ -628,7 +628,7 @@ public virtual void Detect_partially_excluded_shared_table()
[ConditionalFact]
public virtual void Detects_entity_splitting_on_base_type()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToView("Animal").SplitToView("AnimalDetails", s => s.Property(a => a.Name));
modelBuilder.Entity().ToView("Cat");
@@ -640,7 +640,7 @@ public virtual void Detects_entity_splitting_on_base_type()
[ConditionalFact]
public virtual void Detects_entity_splitting_on_derived_type()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToTable("Animal");
modelBuilder.Entity().ToTable("Cat").SplitToTable("CatDetails", s => s.Property(a => a.Name));
@@ -652,7 +652,7 @@ public virtual void Detects_entity_splitting_on_derived_type()
[ConditionalFact]
public virtual void Detects_entity_splitting_with_unmapped_main()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().SplitToView("AnimalDetails", s => s.Property(a => a.Name));
VerifyError(
@@ -663,7 +663,7 @@ public virtual void Detects_entity_splitting_with_unmapped_main()
[ConditionalFact]
public virtual void Detects_entity_splitting_to_with_conflicting_main()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToTable("Animal").SplitToTable("Animal", s => s.Property(a => a.Name));
VerifyError(
@@ -674,7 +674,7 @@ public virtual void Detects_entity_splitting_to_with_conflicting_main()
[ConditionalFact]
public virtual void Detects_entity_splitting_with_unmapped_PK()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().SplitToTable("AnimalDetails", s => s.Property(a => a.Id).HasColumnName(null));
VerifyError(
@@ -685,7 +685,7 @@ public virtual void Detects_entity_splitting_with_unmapped_PK()
[ConditionalFact]
public virtual void Detects_entity_splitting_without_properties()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().SplitToTable("AnimalDetails", s => { });
VerifyError(
@@ -696,7 +696,7 @@ public virtual void Detects_entity_splitting_without_properties()
[ConditionalFact]
public virtual void Detects_entity_splitting_to_table_with_all_properties()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().SplitToTable("AnimalDetails", s =>
{
s.Property(a => a.Name);
@@ -711,7 +711,7 @@ public virtual void Detects_entity_splitting_to_table_with_all_properties()
[ConditionalFact]
public virtual void Detects_entity_splitting_to_view_with_all_properties()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToView("Animal").SplitToView("AnimalDetails", s =>
{
s.Property(a => a.Name);
@@ -723,10 +723,131 @@ public virtual void Detects_entity_splitting_to_view_with_all_properties()
modelBuilder);
}
+ [ConditionalFact]
+ public void Detects_entity_splitting_with_partial_table_splitting()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder.Entity(
+ cb =>
+ {
+ cb.Ignore(c => c.Customer);
+
+ cb.ToTable("Order");
+
+ cb.SplitToTable(
+ "OrderDetails", tb =>
+ {
+ tb.Property(c => c.PartitionId);
+ });
+
+ cb.OwnsOne(
+ c => c.OrderDetails, db =>
+ {
+ db.ToTable("Details");
+
+ db.Property("OtherAddress");
+ db.SplitToTable(
+ "Order", tb =>
+ {
+ tb.Property("OtherAddress");
+ });
+ });
+ cb.Navigation(c => c.OrderDetails).IsRequired();
+ });
+
+ VerifyError(
+ RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(nameof(OrderDetails), "Order", nameof(Order)),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public void Detects_unnamed_index_properties_mapped_to_different_fragments_in_entity_splitting()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder.Entity()
+ .ToTable("Cats")
+ .SplitToTable("Animals", s => s.Property(c => c.Name));
+ modelBuilder.Entity().HasIndex(nameof(Animal.Name), nameof(Cat.Identity));
+
+ var definition = RelationalResources
+ .LogUnnamedIndexPropertiesMappedToNonOverlappingTables(
+ new TestLogger());
+ VerifyWarning(
+ definition.GenerateMessage(
+ nameof(Cat),
+ "{'Name', 'Identity'}",
+ nameof(Animal.Name),
+ "{'Animals'}",
+ nameof(Cat.Identity),
+ "{'Cats'}"),
+ modelBuilder,
+ LogLevel.Error);
+ }
+
+ [ConditionalFact]
+ public void Detects_unnamed_key_properties_mapped_to_different_fragments_in_entity_splitting()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder.Entity()
+ .ToTable("Cats")
+ .SplitToTable("Animals", s => s.Property(c => c.Name));
+ modelBuilder.Entity().HasAlternateKey(nameof(Animal.Name), nameof(Cat.Identity));
+
+ var definition = RelationalResources
+ .LogKeyUnmappedProperties(new TestLogger());
+ VerifyWarning(
+ definition.GenerateMessage(
+ "{'Name', 'Identity'}",
+ nameof(Cat),
+ "Cats"),
+ modelBuilder,
+ LogLevel.Error);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_unmapped_foreign_keys_in_entity_splitting()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity(
+ pb =>
+ {
+ pb.HasKey(p => new {p.Id, p.Name});
+ });
+ modelBuilder.Entity().ToTable("Cat")
+ .HasOne().WithMany()
+ .HasForeignKey("FavoritePersonId", "FavoritePersonName");
+
+ modelBuilder.Entity()
+ .SplitToTable("Animals", s => s.Property("FavoritePersonName"));
+
+ var definition =
+ RelationalResources.LogForeignKeyPropertiesMappedToUnrelatedTables(new TestLogger());
+ VerifyWarning(
+ definition.GenerateMessage(
+ l => l.Log(
+ definition.Level,
+ definition.EventId,
+ definition.MessageFormat,
+ "{'FavoritePersonId', 'FavoritePersonName'}",
+ nameof(Cat),
+ nameof(Person),
+ "{'FavoritePersonId', 'FavoritePersonName'}",
+ nameof(Cat),
+ "{'Id', 'Name'}",
+ nameof(Person))),
+ modelBuilder,
+ LogLevel.Error);
+ }
+
+
+
[ConditionalFact]
public virtual void Detects_duplicate_column_names()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name");
modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").IsRequired();
@@ -741,7 +862,7 @@ public virtual void Detects_duplicate_column_names()
[ConditionalFact]
public virtual void Detects_duplicate_columns_in_derived_types_with_different_types()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").IsRequired();
@@ -756,7 +877,7 @@ public virtual void Detects_duplicate_columns_in_derived_types_with_different_ty
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_MaxLength()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasMaxLength(30);
@@ -771,7 +892,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_IsUnicode()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsUnicode();
@@ -785,7 +906,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_IsFixedLength()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsFixedLength();
@@ -799,7 +920,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_IsConcurrencyToken()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsConcurrencyToken();
@@ -816,7 +937,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_ComputedColumnSql()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasComputedColumnSql("1");
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed");
@@ -830,7 +951,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_stored_setting()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasComputedColumnSql("1", true);
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasComputedColumnSql("1");
@@ -844,7 +965,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_DefaultValue()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasDefaultValueSql("1");
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasDefaultValue("1");
@@ -858,7 +979,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_DefaultValueSql()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasDefaultValueSql("1");
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed");
@@ -872,7 +993,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_nullability()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity();
modelBuilder.Entity().Property("OtherId").HasColumnName("Id");
@@ -886,7 +1007,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Passes_on_duplicate_column_names_within_hierarchy_with_same_column_nullability()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property("OtherId").HasColumnName("OtherId");
modelBuilder.Entity().Property("OtherId").HasColumnName("OtherId");
@@ -903,7 +1024,7 @@ public virtual void Passes_on_duplicate_column_names_within_hierarchy_with_same_
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_comments()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasComment("My comment");
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed");
@@ -917,7 +1038,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_collations()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").UseCollation("UTF8");
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed");
@@ -931,7 +1052,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_orders()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasColumnOrder(0);
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed");
@@ -945,7 +1066,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_precision()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasPrecision(1);
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed");
@@ -959,7 +1080,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_scale()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasPrecision(1, 2);
modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasPrecision(1);
@@ -973,7 +1094,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
[ConditionalFact]
public virtual void Passes_for_compatible_duplicate_column_names_within_hierarchy()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
modelBuilder.Entity(
eb =>
@@ -1000,7 +1121,7 @@ public virtual void Passes_for_compatible_duplicate_column_names_within_hierarch
[ConditionalFact]
public virtual void Passes_for_shared_columns()
{
- var modelBuilder = CreateConventionalModelBuilder();
+ var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity