Skip to content

Commit

Permalink
Add TPC support to update pipeline
Browse files Browse the repository at this point in the history
Switch the update pipeline to use the relational model for command ordering, this is required for a robust implementation of seed data support for TPC
Add provider value comparer

Part of #3170
  • Loading branch information
AndriySvyryd committed Apr 5, 2022
1 parent 2500217 commit 246cc86
Show file tree
Hide file tree
Showing 87 changed files with 2,728 additions and 996 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ public class EntityFrameworkRelationalServicesBuilder : EntityFrameworkServicesB
public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServices
= new Dictionary<Type, ServiceCharacteristics>
{
{ typeof(IKeyValueIndexFactorySource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IRowKeyValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IRowForeignKeyValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IRowIndexValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IParameterNameGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IComparer<IReadOnlyModificationCommand>), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMigrationsIdGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
Expand Down Expand Up @@ -125,7 +127,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IParameterNameGeneratorFactory, ParameterNameGeneratorFactory>();
TryAdd<IComparer<IReadOnlyModificationCommand>, ModificationCommandComparer>();
TryAdd<IMigrationsIdGenerator, MigrationsIdGenerator>();
TryAdd<IKeyValueIndexFactorySource, KeyValueIndexFactorySource>();
TryAdd<IRowKeyValueFactoryFactory, RowKeyValueFactoryFactory>();
TryAdd<IRowForeignKeyValueFactoryFactory, RowForeignKeyValueFactoryFactory>();
TryAdd<IRowIndexValueFactoryFactory, RowIndexValueFactoryFactory>();
TryAdd<IModelCustomizer, RelationalModelCustomizer>();
TryAdd<IModelRuntimeInitializer, RelationalModelRuntimeInitializer>();
TryAdd<IRelationalAnnotationProvider, RelationalAnnotationProvider>();
Expand Down
Original file line number Diff line number Diff line change
@@ -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 Microsoft.EntityFrameworkCore.Update.Internal;

namespace Microsoft.EntityFrameworkCore.Infrastructure;

/// <summary>
Expand Down Expand Up @@ -45,7 +47,40 @@ public sealed record RelationalModelDependencies
/// the constructor at any point in this process.
/// </remarks>
[EntityFrameworkInternal]
public RelationalModelDependencies()
public RelationalModelDependencies(
IRowKeyValueFactoryFactory rowKeyValueFactoryFactory,
IRowForeignKeyValueFactoryFactory foreignKeyRowValueFactorySource,
IRowIndexValueFactoryFactory rowIndexValueFactoryFactory)
{
RowKeyValueFactoryFactory = rowKeyValueFactoryFactory;
RowForeignKeyValueFactoryFactory = foreignKeyRowValueFactorySource;
RowIndexValueFactoryFactory = rowIndexValueFactoryFactory;
}

/// <summary>
/// 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.
/// </summary>
[EntityFrameworkInternal]
public IRowKeyValueFactoryFactory RowKeyValueFactoryFactory { get; init; }

/// <summary>
/// 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.
/// </summary>
[EntityFrameworkInternal]
public IRowForeignKeyValueFactoryFactory RowForeignKeyValueFactoryFactory { get; init; }

/// <summary>
/// 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.
/// </summary>
[EntityFrameworkInternal]
public IRowIndexValueFactoryFactory RowIndexValueFactoryFactory { get; init; }
}
40 changes: 29 additions & 11 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -559,10 +559,10 @@ protected virtual void ValidateSharedViewCompatibility(
mappedTypes.Add(entityType);
}

foreach (var (table, mappedTypes) in views)
foreach (var (view, mappedTypes) in views)
{
ValidateSharedViewCompatibility(mappedTypes, table.Name, table.Schema, logger);
ValidateSharedColumnsCompatibility(mappedTypes, table, logger);
ValidateSharedViewCompatibility(mappedTypes, view.Name, view.Schema, logger);
ValidateSharedColumnsCompatibility(mappedTypes, view, logger);
}
}

Expand Down Expand Up @@ -866,10 +866,12 @@ protected virtual void ValidateCompatible(
storeObject.DisplayName()));
}

var typeMapping = property.GetRelationalTypeMapping();
var duplicateTypeMapping = duplicateProperty.GetRelationalTypeMapping();
var currentTypeString = property.GetColumnType(storeObject)
?? property.GetRelationalTypeMapping().StoreType;
?? typeMapping.StoreType;
var previousTypeString = duplicateProperty.GetColumnType(storeObject)
?? duplicateProperty.GetRelationalTypeMapping().StoreType;
?? duplicateTypeMapping.StoreType;
if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
Expand All @@ -884,6 +886,22 @@ protected virtual void ValidateCompatible(
currentTypeString));
}

var currentProviderType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType;
var previousProviderType = duplicateTypeMapping.Converter?.ProviderClrType ?? duplicateTypeMapping.ClrType;
if (currentProviderType != previousProviderType)
{
throw new InvalidOperationException(
RelationalStrings.DuplicateColumnNameProviderTypeMismatch(
duplicateProperty.DeclaringEntityType.DisplayName(),
duplicateProperty.Name,
property.DeclaringEntityType.DisplayName(),
property.Name,
columnName,
storeObject.DisplayName(),
previousProviderType.ShortDisplayName(),
currentProviderType.ShortDisplayName()));
}

var currentComputedColumnSql = property.GetComputedColumnSql(storeObject) ?? "";
var previousComputedColumnSql = duplicateProperty.GetComputedColumnSql(storeObject) ?? "";
if (!currentComputedColumnSql.Equals(previousComputedColumnSql, StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -1340,8 +1358,8 @@ protected override void ValidateInheritanceMapping(
RelationalStrings.NonTphMappingStrategy(mappingStrategy, entityType.DisplayName()));
}

ValidateTPHMapping(entityType, forTables: false);
ValidateTPHMapping(entityType, forTables: true);
ValidateTphMapping(entityType, forTables: false);
ValidateTphMapping(entityType, forTables: true);
ValidateDiscriminatorValues(entityType);
}
else
Expand All @@ -1362,8 +1380,8 @@ protected override void ValidateInheritanceMapping(
RelationalStrings.KeylessMappingStrategy(mappingStrategy ?? RelationalAnnotationNames.TptMappingStrategy, entityType.DisplayName()));
}

ValidateNonTPHMapping(entityType, forTables: false);
ValidateNonTPHMapping(entityType, forTables: true);
ValidateNonTphMapping(entityType, forTables: false);
ValidateNonTphMapping(entityType, forTables: true);
}
}
}
Expand All @@ -1387,7 +1405,7 @@ protected virtual void ValidateMappingStrategy(string? mappingStrategy, IEntityT
};
}

private static void ValidateNonTPHMapping(IEntityType rootEntityType, bool forTables)
private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTables)
{
var derivedTypes = new Dictionary<(string, string?), IEntityType>();
foreach (var entityType in rootEntityType.GetDerivedTypesInclusive())
Expand All @@ -1413,7 +1431,7 @@ private static void ValidateNonTPHMapping(IEntityType rootEntityType, bool forTa
}
}

private static void ValidateTPHMapping(IEntityType rootEntityType, bool forTables)
private static void ValidateTphMapping(IEntityType rootEntityType, bool forTables)
{
string? firstName = null;
string? firstSchema = null;
Expand Down
24 changes: 21 additions & 3 deletions src/EFCore.Relational/Metadata/IColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public interface IColumn : IColumnBase
/// <summary>
/// Gets the property mappings.
/// </summary>
new IEnumerable<IColumnMapping> PropertyMappings { get; }
new IReadOnlyList<IColumnMapping> PropertyMappings { get; }

/// <summary>
/// Gets the maximum length of data that is allowed in this column. For example, if the property is a <see cref="string" /> '
Expand Down Expand Up @@ -98,8 +98,7 @@ public virtual bool TryGetDefaultValue(out object? defaultValue)
continue;
}

var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping.Converter;

var converter = property.GetValueConverter() ?? mapping.TypeMapping.Converter;
if (converter != null)
{
defaultValue = converter.ConvertToProvider(defaultValue);
Expand Down Expand Up @@ -148,6 +147,25 @@ public virtual string? Collation
=> PropertyMappings.First().Property
.GetCollation(StoreObjectIdentifier.Table(Table.Name, Table.Schema));

/// <summary>
/// Returns the property mapping for the given entity type.
/// </summary>
/// <param name="entityType">An entity type.</param>
/// <returns>The property mapping or <see langword="null" /> if not found.</returns>
public virtual IColumnMapping? FindColumnMapping(IReadOnlyEntityType entityType)
{
for (var i = 0; i < PropertyMappings.Count; i++)
{
var mapping = PropertyMappings[i];
if (mapping.Property.DeclaringEntityType.IsAssignableFrom(entityType))
{
return mapping;
}
}

return null;
}

/// <summary>
/// <para>
/// Creates a human-readable representation of the given metadata.
Expand Down
7 changes: 6 additions & 1 deletion src/EFCore.Relational/Metadata/IColumnBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public interface IColumnBase : IAnnotatable
/// </summary>
string StoreType { get; }

/// <summary>
/// Gets the provider type.
/// </summary>
Type ProviderClrType { get; }

/// <summary>
/// Gets the value indicating whether the column can contain NULL.
/// </summary>
Expand All @@ -34,5 +39,5 @@ public interface IColumnBase : IAnnotatable
/// <summary>
/// Gets the property mappings.
/// </summary>
IEnumerable<IColumnMappingBase> PropertyMappings { get; }
IReadOnlyList<IColumnMappingBase> PropertyMappings { get; }
}
11 changes: 8 additions & 3 deletions src/EFCore.Relational/Metadata/IForeignKeyConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ public interface IForeignKeyConstraint : IAnnotatable
/// <summary>
/// Gets the columns that are referenced by the foreign key constraint.
/// </summary>
IReadOnlyList<IColumn> PrincipalColumns { get; }
IReadOnlyList<IColumn> PrincipalColumns => PrincipalUniqueConstraint.Columns;

/// <summary>
/// Gets the unique constraint on the columns referenced by the foreign key constraint.
/// </summary>
IUniqueConstraint PrincipalUniqueConstraint { get; }

/// <summary>
/// Gets the action to be performed when the referenced row is deleted.
Expand Down Expand Up @@ -78,11 +83,11 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
.Append(' ')
.Append(Table.Name)
.Append(' ')
.Append(ColumnBase.Format(Columns))
.Append(ColumnBase<IColumnMappingBase>.Format(Columns))
.Append(" -> ")
.Append(PrincipalTable.Name)
.Append(' ')
.Append(ColumnBase.Format(PrincipalColumns));
.Append(ColumnBase<IColumnMappingBase>.Format(PrincipalColumns));

if (OnDeleteAction != ReferentialAction.NoAction)
{
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/IFunctionColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public interface IFunctionColumn : IColumnBase
/// <summary>
/// Gets the property mappings.
/// </summary>
new IEnumerable<IFunctionColumnMapping> PropertyMappings { get; }
new IReadOnlyList<IFunctionColumnMapping> PropertyMappings { get; }

/// <summary>
/// <para>
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/ISqlQueryColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface ISqlQueryColumn : IColumnBase
/// <summary>
/// Gets the property mappings.
/// </summary>
new IEnumerable<ISqlQueryColumnMapping> PropertyMappings { get; }
new IReadOnlyList<ISqlQueryColumnMapping> PropertyMappings { get; }

/// <summary>
/// <para>
Expand Down
5 changes: 5 additions & 0 deletions src/EFCore.Relational/Metadata/ITable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public interface ITable : ITableBase
/// </summary>
IEnumerable<IForeignKeyConstraint> ForeignKeyConstraints { get; }

/// <summary>
/// Gets the foreign key constraints referencing this table.
/// </summary>
IEnumerable<IForeignKeyConstraint> ReferencingForeignKeyConstraints { get; }

/// <summary>
/// Gets the unique constraints including the primary key for this table.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore.Relational/Metadata/ITableBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public interface ITableBase : IAnnotatable
/// </summary>
string? Schema { get; }

/// <summary>
/// Gets the schema-qualified name of the table in the database.
/// </summary>
string SchemaQualifiedName
=> Schema == null ? Name : Schema + "." + Name;

/// <summary>
/// Gets the database model.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/IUniqueConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
builder
.Append(Name)
.Append(' ')
.Append(ColumnBase.Format(Columns));
.Append(ColumnBase<IColumnMappingBase>.Format(Columns));

if (GetIsPrimaryKey())
{
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/IViewColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public interface IViewColumn : IColumnBase
/// <summary>
/// Gets the property mappings.
/// </summary>
new IEnumerable<IViewColumnMapping> PropertyMappings { get; }
new IReadOnlyList<IViewColumnMapping> PropertyMappings { get; }

/// <summary>
/// <para>
Expand Down
23 changes: 20 additions & 3 deletions src/EFCore.Relational/Metadata/Internal/Column.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// 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.Internal;
using Microsoft.EntityFrameworkCore.Update.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Internal;

/// <summary>
Expand All @@ -9,8 +12,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
/// 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.
/// </summary>
public class Column : ColumnBase, IColumn
public class Column : ColumnBase<ColumnMapping>, IColumn
{
// Warning: Never access these fields directly as access needs to be thread-safe
private ColumnAccessors? _accessors;

/// <summary>
/// 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
Expand All @@ -31,6 +37,17 @@ public Column(string name, string type, Table table)
public new virtual Table Table
=> (Table)base.Table;

/// <summary>
/// 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.
/// </summary>
public virtual ColumnAccessors Accessors
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _accessors, this, static column =>
ColumnAccessorsFactory.Create(column));

/// <summary>
/// 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
Expand All @@ -48,9 +65,9 @@ ITable IColumn.Table
}

/// <inheritdoc />
IEnumerable<IColumnMapping> IColumn.PropertyMappings
IReadOnlyList<IColumnMapping> IColumn.PropertyMappings
{
[DebuggerStepThrough]
get => PropertyMappings.Cast<IColumnMapping>();
get => PropertyMappings;
}
}
Loading

0 comments on commit 246cc86

Please sign in to comment.