Skip to content

Commit

Permalink
Add complex type support to the update pipeline
Browse files Browse the repository at this point in the history
Fixes #29751
Part of #13947
  • Loading branch information
AndriySvyryd committed Aug 6, 2023
1 parent 2f95488 commit a72dea7
Show file tree
Hide file tree
Showing 47 changed files with 2,575 additions and 454 deletions.
34 changes: 17 additions & 17 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,13 @@ protected virtual void GenerateSequence(
if (sequence.Type != Sequence.DefaultClrType)
{
sequenceBuilderNameBuilder
.Append("<")
.Append('<')
.Append(Code.Reference(sequence.Type))
.Append(">");
.Append('>');
}

sequenceBuilderNameBuilder
.Append("(")
.Append('(')
.Append(Code.Literal(sequence.Name));

if (!string.IsNullOrEmpty(sequence.ModelSchema))
Expand All @@ -331,7 +331,7 @@ protected virtual void GenerateSequence(
.Append(Code.Literal(sequence.ModelSchema));
}

sequenceBuilderNameBuilder.Append(")");
sequenceBuilderNameBuilder.Append(')');
var sequenceBuilderName = sequenceBuilderNameBuilder.ToString();

stringBuilder
Expand All @@ -347,7 +347,7 @@ protected virtual void GenerateSequence(
.AppendLine()
.Append(".StartsAt(")
.Append(Code.Literal(sequence.StartValue))
.Append(")");
.Append(')');
}

if (sequence.IncrementBy != Sequence.DefaultIncrementBy)
Expand All @@ -356,7 +356,7 @@ protected virtual void GenerateSequence(
.AppendLine()
.Append(".IncrementsBy(")
.Append(Code.Literal(sequence.IncrementBy))
.Append(")");
.Append(')');
}

if (sequence.MinValue != Sequence.DefaultMinValue)
Expand All @@ -374,7 +374,7 @@ protected virtual void GenerateSequence(
.AppendLine()
.Append(".HasMax(")
.Append(Code.Literal(sequence.MaxValue))
.Append(")");
.Append(')');
}

if (sequence.IsCyclic != Sequence.DefaultIsCyclic)
Expand Down Expand Up @@ -586,7 +586,7 @@ protected virtual void GenerateComplexProperty(

private static string GenerateNestedBuilderName(string builderName)
{
if (builderName.StartsWith("b", StringComparison.Ordinal))
if (builderName.StartsWith('b'))
{
// ReSharper disable once InlineOutVariableDeclaration
var counter = 1;
Expand Down Expand Up @@ -876,7 +876,7 @@ protected virtual void GenerateEntityTypeAnnotations(
stringBuilder
.AppendLine()
.Append(entityTypeBuilderName)
.Append(".")
.Append('.')
.Append("HasDiscriminator");

if (discriminatorPropertyAnnotation?.Value != null)
Expand All @@ -886,11 +886,11 @@ protected virtual void GenerateEntityTypeAnnotations(
.MakeNullable(discriminatorProperty.IsNullable)
?? discriminatorProperty.ClrType;
stringBuilder
.Append("<")
.Append('<')
.Append(Code.Reference(propertyClrType))
.Append(">(")
.Append(Code.Literal((string)discriminatorPropertyAnnotation.Value))
.Append(")");
.Append(')');
}
else
{
Expand All @@ -903,11 +903,11 @@ protected virtual void GenerateEntityTypeAnnotations(
var value = (bool)discriminatorMappingCompleteAnnotation.Value;

stringBuilder
.Append(".")
.Append('.')
.Append("IsComplete")
.Append("(")
.Append('(')
.Append(Code.Literal(value))
.Append(")");
.Append(')');
}

if (discriminatorValueAnnotation?.Value != null)
Expand All @@ -924,11 +924,11 @@ protected virtual void GenerateEntityTypeAnnotations(
}

stringBuilder
.Append(".")
.Append('.')
.Append("HasValue")
.Append("(")
.Append('(')
.Append(Code.UnknownLiteral(value))
.Append(")");
.Append(')');
}

stringBuilder.AppendLine(";");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Text;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal;
Expand Down Expand Up @@ -652,7 +651,7 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator
mainBuilder.AppendLine(",")
.Append("indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(")
.Append(_code.Literal(entityType.ClrType))
.Append(")");
.Append(')');
}

if (entityType.IsPropertyBag)
Expand Down Expand Up @@ -991,7 +990,7 @@ private void PropertyBaseParameters(
.Append(".GetProperty(")
.Append(_code.Literal(propertyInfo.Name))
.Append(", ")
.Append(propertyInfo.GetAccessors().Any() ? "BindingFlags.Public" : "BindingFlags.NonPublic")
.Append(propertyInfo.GetAccessors().Length != 0 ? "BindingFlags.Public" : "BindingFlags.NonPublic")
.Append(propertyInfo.IsStatic() ? " | BindingFlags.Static" : " | BindingFlags.Instance")
.Append(" | BindingFlags.DeclaredOnly)");
}
Expand Down Expand Up @@ -1055,12 +1054,12 @@ private void FindProperties(
.Append(entityTypeVariable)
.Append(".FindProperty(")
.Append(_code.Literal(property.Name))
.Append(")");
.Append(')');

if (nullable)
{
mainBuilder
.Append("!");
.Append('!');
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,16 @@ private static void TryUniquifyColumnNames(
in StoreObjectIdentifier storeObject,
int maxLength)
{
foreach (var property in type.GetDeclaredProperties())
foreach (var property in type.GetProperties())
{
var columnName = property.GetColumnName(storeObject);
if (columnName == null)
{
continue;
}

if (!columns.TryGetValue(columnName, out var otherProperty))
if (!columns.TryGetValue(columnName, out var otherProperty)
|| property == otherProperty)
{
columns[columnName] = property;
continue;
Expand All @@ -237,13 +238,16 @@ private static void TryUniquifyColumnNames(
|| (property.IsConcurrencyToken && otherProperty.IsConcurrencyToken)
|| (!property.Builder.CanSetColumnName(null) && !otherProperty.Builder.CanSetColumnName(null)))
{
// Handle this with a default value convention #9329
if (property.GetAfterSaveBehavior() == PropertySaveBehavior.Save
&& otherProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save
&& property.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes
&& otherProperty.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes)
&& property.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes)
{
// Handle this with a default value convention #9329
property.Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes);
}

if (otherProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save
&& otherProperty.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes)
{
otherProperty.Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.

using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;

/// <summary>
Expand Down Expand Up @@ -79,6 +77,8 @@ public virtual void ProcessModelFinalizing(
Dictionary<IConventionEntityType, IReadOnlyProperty>? entityTypesMissingConcurrencyColumn = null;
foreach (var entityType in mappedTypes)
{
Check.DebugAssert(readOnlyProperties.Count != 0, $"No properties mapped to column '{concurrencyColumnName}'");

var foundMappedProperty = !IsConcurrencyTokenMissing(readOnlyProperties, entityType, mappedTypes)
|| entityType.GetProperties()
.Any(p => p.GetColumnName(StoreObjectIdentifier.Table(name, schema)) == concurrencyColumnName);
Expand All @@ -87,12 +87,8 @@ public virtual void ProcessModelFinalizing(
{
entityTypesMissingConcurrencyColumn ??= new Dictionary<IConventionEntityType, IReadOnlyProperty>();

// store the entity type which is missing the
// concurrency token property, mapped to an example
// property which _is_ mapped to this concurrency token
// column and which will be used later as a template
entityTypesMissingConcurrencyColumn.Add(
entityType, readOnlyProperties.First());
// store the concurrency token property to be used later as a template
entityTypesMissingConcurrencyColumn.Add(entityType, readOnlyProperties.First());
}
}

Expand Down Expand Up @@ -168,7 +164,17 @@ public virtual void ProcessModelFinalizing(
nonHierarchyTypesCount++;
}

foreach (var property in entityType.GetDeclaredProperties())
concurrencyColumns = FindConcurrencyColumns(entityType, storeObject, concurrencyColumns);
}

return nonHierarchyTypesCount < 2 ? null : concurrencyColumns;

static Dictionary<string, List<IReadOnlyProperty>>? FindConcurrencyColumns(
IReadOnlyTypeBase structuralType,
StoreObjectIdentifier storeObject,
Dictionary<string, List<IReadOnlyProperty>>? concurrencyColumns)
{
foreach (var property in structuralType.GetDeclaredProperties())
{
if (!property.IsConcurrencyToken
|| (property.ValueGenerated & ValueGenerated.OnUpdate) == 0)
Expand All @@ -183,7 +189,6 @@ public virtual void ProcessModelFinalizing(
}

concurrencyColumns ??= new Dictionary<string, List<IReadOnlyProperty>>();

if (!concurrencyColumns.TryGetValue(columnName, out var properties))
{
properties = new List<IReadOnlyProperty>();
Expand All @@ -192,9 +197,14 @@ public virtual void ProcessModelFinalizing(

properties.Add(property);
}
}

return nonHierarchyTypesCount < 2 ? null : concurrencyColumns;
foreach (var complexProperty in structuralType.GetDeclaredComplexProperties())
{
concurrencyColumns = FindConcurrencyColumns(complexProperty.ComplexType, storeObject, concurrencyColumns);
}

return concurrencyColumns;
}
}

/// <summary>
Expand All @@ -209,29 +219,26 @@ public static bool IsConcurrencyTokenMissing(
IReadOnlyEntityType entityType,
IReadOnlyList<IReadOnlyEntityType> mappedTypes)
{
if (entityType.FindPrimaryKey() == null
|| propertiesMappedToConcurrencyColumn.Count == 0)
if (entityType.FindPrimaryKey() == null)
{
return false;
}

var propertyMissing = true;
foreach (var mappedProperty in propertiesMappedToConcurrencyColumn)
{
var declaringType = mappedProperty.DeclaringType;
var declaringEntityType = declaringType as IEntityType;
if (declaringType.IsAssignableFrom(entityType)
|| entityType.IsAssignableFrom(declaringType)
|| declaringEntityType != null
&& (declaringEntityType.IsInOwnershipPath(entityType)
|| entityType.IsInOwnershipPath(declaringEntityType)))
var containingEntityType = mappedProperty.DeclaringType.ContainingEntityType;
if (containingEntityType.IsAssignableFrom(entityType)
|| entityType.IsAssignableFrom(containingEntityType)
|| containingEntityType.IsInOwnershipPath(entityType)
|| entityType.IsInOwnershipPath(containingEntityType))
{
// The concurrency token is on the base type, derived type or in the same aggregate
// The concurrency token is on the base type, derived type, a contained complex type or in the same aggregate
propertyMissing = false;
continue;
}

var linkingFks = declaringEntityType?.FindForeignKeys(declaringEntityType.FindPrimaryKey()!.Properties)
var linkingFks = containingEntityType.FindForeignKeys(containingEntityType.FindPrimaryKey()!.Properties)
.Where(fk => fk.PrincipalKey.IsPrimaryKey()
&& mappedTypes.Contains(fk.PrincipalEntityType)).ToList();
if (linkingFks != null
Expand Down Expand Up @@ -259,9 +266,8 @@ private static void RemoveDerivedEntityTypes(
var baseType = entityType.BaseType;
while (baseType != null)
{
if (entityTypeDictionary.ContainsKey(baseType))
if (entityTypeDictionary.Remove(entityType))
{
entityTypeDictionary.Remove(entityType);
removed = true;
break;
}
Expand Down
6 changes: 4 additions & 2 deletions src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,10 @@ private static void AddSqlQueries(RelationalModel databaseModel, IEntityType ent
var column = sqlQuery.FindColumn(columnName);
if (column == null)
{
column = new SqlQueryColumn(columnName, property.GetColumnType(mappedQuery), sqlQuery);
column.IsNullable = property.IsColumnNullable(mappedQuery);
column = new SqlQueryColumn(columnName, property.GetColumnType(mappedQuery), sqlQuery)
{
IsNullable = property.IsColumnNullable(mappedQuery)
};
sqlQuery.Columns.Add(columnName, column);
}
else if (!property.IsColumnNullable(mappedQuery))
Expand Down
Loading

0 comments on commit a72dea7

Please sign in to comment.