Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ private static void UniquifyColumnNames(
var declaringEntityType = property.DeclaringType as IConventionEntityType;
#pragma warning disable EF1001 // Internal EF Core API usage.
var identifyingMemberInfo = property.GetIdentifyingMemberInfo();
var isInheritedSharedMember = identifyingMemberInfo != null
var isInheritedSharedMember =
type is not IConventionComplexType
&& identifyingMemberInfo != null
&& ((declaringEntityType != null && identifyingMemberInfo.DeclaringType != type.ClrType)
|| (declaringEntityType == null
&& otherProperty.DeclaringType is IConventionComplexType otherDeclaringComplexType
Expand Down Expand Up @@ -279,26 +281,28 @@ private static void UniquifyColumnNames(
continue;
}

var usePrefix = property.DeclaringType != otherProperty.DeclaringType;
if (!usePrefix
var differentTypes = property.DeclaringType != otherProperty.DeclaringType;
if (!differentTypes
|| (!property.DeclaringType.IsStrictlyDerivedFrom(otherProperty.DeclaringType)
&& !otherProperty.DeclaringType.IsStrictlyDerivedFrom(property.DeclaringType))
|| declaringEntityType?.FindRowInternalForeignKeys(storeObject).Any() == true)
{
var newColumnName = TryUniquify(property, columnName, columns, storeObject, usePrefix, maxLength);
var prefix = CreatePrefix(property.DeclaringType, otherProperty.DeclaringType);
var newColumnName = TryUniquify(property, columnName, columns, storeObject, prefix, maxLength);
if (newColumnName != null)
{
columns[newColumnName] = property;
continue;
}
}

if (!usePrefix
if (!differentTypes
|| (!property.DeclaringType.IsStrictlyDerivedFrom(otherProperty.DeclaringType)
&& !otherProperty.DeclaringType.IsStrictlyDerivedFrom(property.DeclaringType))
|| (otherProperty.DeclaringType as IConventionEntityType)?.FindRowInternalForeignKeys(storeObject).Any() == true)
{
var newOtherColumnName = TryUniquify(otherProperty, columnName, columns, storeObject, usePrefix, maxLength);
var prefix = CreatePrefix(otherProperty.DeclaringType, property.DeclaringType);
var newOtherColumnName = TryUniquify(otherProperty, columnName, columns, storeObject, prefix, maxLength);
if (newOtherColumnName != null)
{
columns[columnName] = property;
Expand All @@ -307,30 +311,36 @@ private static void UniquifyColumnNames(
}
}

foreach (var complexProperty in type.GetDeclaredComplexProperties())
foreach (var complexProperty in type.GetComplexProperties())
{
UniquifyColumnNames(complexProperty.ComplexType, columns, storeObject, maxLength);
}

static string CreatePrefix(IReadOnlyTypeBase type, IReadOnlyTypeBase otherType)
{
var prefix = type.ShortName();
return prefix == otherType.ShortName()
&& type is IComplexType complexType
&& otherType is IComplexType othertComplexType
? CreatePrefix(complexType.ComplexProperty.DeclaringType, othertComplexType.ComplexProperty.DeclaringType)
: prefix;
}
}

private static string? TryUniquify(
IConventionProperty property,
string columnName,
Dictionary<string, IConventionProperty> properties,
in StoreObjectIdentifier storeObject,
bool usePrefix,
string? prefix,
int maxLength)
{
if (property.Builder.CanSetColumnName(null)
&& property.Builder.CanSetColumnName(null, storeObject))
{
if (usePrefix)
if (prefix != null)
{
var prefix = property.DeclaringType.ShortName();
if (!columnName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
columnName = prefix + "_" + columnName;
}
columnName = prefix + "_" + columnName;
}

columnName = Uniquifier.Uniquify(columnName, properties, maxLength);
Expand Down
75 changes: 54 additions & 21 deletions src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection.Emit;
using System.Text;

namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
Expand Down Expand Up @@ -544,9 +545,7 @@ private static void CreateTableMapping(
}
}

// TODO: Change this to call GetComplexProperties()
// Issue #31248
foreach (var complexProperty in mappedType.GetDeclaredComplexProperties())
foreach (var complexProperty in mappedType.GetComplexProperties())
{
var complexType = complexProperty.ComplexType;

Expand All @@ -557,6 +556,10 @@ private static void CreateTableMapping(
complexTableMappings = [];
complexType.AddRuntimeAnnotation(RelationalAnnotationNames.TableMappings, complexTableMappings);
}
else if (complexTableMappings.Any(m => m.Table == table))
{
continue;
}

CreateTableMapping(
relationalTypeMappingSource,
Expand Down Expand Up @@ -606,8 +609,11 @@ private static void CreateContainerColumn<TColumnMappingBase>(
return;
}

Check.DebugAssert(
tableBase.FindColumn(containerColumnName) == null, $"Table '{tableBase.Name}' already has a '{containerColumnName}' column.");
if (tableBase.FindColumn(containerColumnName) != null)
{
// TODO: Add column mapping #36646
return;
}

var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(
typeof(JsonTypePlaceholder), storeTypeName: containerColumnType);
Expand All @@ -624,9 +630,11 @@ private static void CreateContainerColumn<TColumnMappingBase>(
{
jsonColumn.IsNullable = !ownership.IsRequiredDependent || !ownership.IsUnique;

if (ownership.PrincipalEntityType.BaseType != null)
if (!jsonColumn.IsNullable
&& ownership.PrincipalEntityType.BaseType != null
&& ownership.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
{
// if navigation is defined on a derived type, the column must be made nullable
// if navigation is defined on a derived type in TPH, the column must be made nullable
jsonColumn.IsNullable = true;
}
}
Expand All @@ -637,6 +645,16 @@ private static void CreateContainerColumn<TColumnMappingBase>(
jsonColumn.IsNullable = complexType.ComplexProperty.IsNullable
|| complexType.ComplexProperty.GetChainToComplexProperty(fromEntity: true).Any(p => p.IsNullable);
#pragma warning restore EF1001 // Internal EF Core API usage.

var declaringType = complexType.ComplexProperty.DeclaringType;
if (!jsonColumn.IsNullable
&& declaringType is IEntityType declaringEntityType
&& declaringEntityType.BaseType != null
&& declaringEntityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
{
// if complex property is defined on a derived type in TPH, the column must be made nullable
jsonColumn.IsNullable = true;
}
}
}

Expand All @@ -658,6 +676,9 @@ private static void AddViews(

var mappingStrategy = entityType.GetMappingStrategy();
var isTpc = mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy;
var includesDerivedTypes = entityType.GetDirectlyDerivedTypes().Any()
? !isTpc && mappedType == entityType
: (bool?)null;
while (mappedType != null)
{
var mappedViewName = mappedType.GetViewName();
Expand All @@ -674,9 +695,6 @@ private static void AddViews(
continue;
}

var includesDerivedTypes = entityType.GetDirectlyDerivedTypes().Any()
? !isTpc && mappedType == entityType
: (bool?)null;
foreach (var fragment in mappedType.GetMappingFragments(StoreObjectType.View))
{
CreateViewMapping(
Expand Down Expand Up @@ -770,9 +788,7 @@ private static void CreateViewMapping(
}
}

// TODO: Change this to call GetComplexProperties()
// Issue #31248
foreach (var complexProperty in mappedType.GetDeclaredComplexProperties())
foreach (var complexProperty in mappedType.GetComplexProperties())
{
var complexType = complexProperty.ComplexType;

Expand All @@ -783,6 +799,10 @@ private static void CreateViewMapping(
complexViewMappings = [];
complexType.AddRuntimeAnnotation(RelationalAnnotationNames.ViewMappings, complexViewMappings);
}
else if (complexViewMappings.Any(m => m.Table == view))
{
continue;
}

CreateViewMapping(
relationalTypeMappingSource,
Expand Down Expand Up @@ -1677,21 +1697,34 @@ private static void PopulateRowInternalForeignKeys<TColumnMapping>(TableBase tab

if (table.EntityTypeMappings.Single(etm => etm.TypeBase == typeBase).IncludesDerivedTypes == true)
{
foreach (var directlyDerivedEntityType in entityType.GetDirectlyDerivedTypes())
{
if (mappedEntityTypes.Contains(directlyDerivedEntityType)
&& !optionalTypes.ContainsKey(directlyDerivedEntityType))
{
entityTypesToVisit.Enqueue((directlyDerivedEntityType, optional));
}
}
EnqueueDerivedTypes(entityType, mappedEntityTypes, optionalTypes, entityTypesToVisit, optional);
}
}

if (optionalTypes.Count > 1)
{
table.OptionalTypes = optionalTypes;
}

static void EnqueueDerivedTypes(
IEntityType entityType,
HashSet<IEntityType> mappedEntityTypes,
Dictionary<ITypeBase, bool> optionalTypes,
Queue<(ITypeBase, bool)> entityTypesToVisit,
bool optional)
{
foreach (var directlyDerivedEntityType in entityType.GetDirectlyDerivedTypes())
{
if (!mappedEntityTypes.Contains(directlyDerivedEntityType))
{
EnqueueDerivedTypes(directlyDerivedEntityType, mappedEntityTypes, optionalTypes, entityTypesToVisit, optional);
}
else if (!optionalTypes.ContainsKey(directlyDerivedEntityType))
{
entityTypesToVisit.Enqueue((directlyDerivedEntityType, optional));
}
}
}
}

private static void PopulateForeignKeyConstraints(Table table)
Expand Down
24 changes: 24 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@
<data name="CannotSetAliasOnJoin" xml:space="preserve">
<value>Aliases cannot be set on join expressions; set the alias on the enclosed table expression.</value>
</data>
<data name="CannotCompareJsonComplexTypeToNonJson" xml:space="preserve">
<value>Cannot compare complex type '{jsonComplexType}', which is mapped to JSON, to complex type '{nonJsonComplexType}', which is not.</value>
</data>
<data name="CannotTranslateNonConstantNewArrayExpression" xml:space="preserve">
<value>The query contains a new array expression with non-constant elements that cannot be translated: '{newArrayExpression}'.</value>
</data>
Expand Down Expand Up @@ -469,6 +472,12 @@
<data name="ImplicitDefaultNamesNotSupportedForTpcWhenNamesClash" xml:space="preserve">
<value>Named default constraints cannot be used with TPC or entity splitting if they result in non-unique constraint names. Constraint name: '{constraintNameCandidate}'.</value>
</data>
<data name="IncompatibleComplexTypesInComparison" xml:space="preserve">
<value>The complex types '{complexType1}' and '{complexType2}' are being compared, but the latter is lacking property '{property}' of the former.</value>
</data>
<data name="IncompatibleComplexTypesInAssignment" xml:space="preserve">
<value>The complex types '{complexType1}' and '{complexType2}' are being assigned, but the latter is lacking property '{property}' of the former.</value>
</data>
<data name="IncompatibleTableCommentMismatch" xml:space="preserve">
<value>The table '{table}' cannot be used for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and the comment '{comment}' does not match the comment '{otherComment}'.</value>
</data>
Expand Down
Loading
Loading