Skip to content

Commit

Permalink
Add a relational model API
Browse files Browse the repository at this point in the history
Use the new model in migrations and update pipeline

Part of #12846, #2725, #8258, #15671, #17270
  • Loading branch information
AndriySvyryd committed Feb 12, 2020
1 parent 3574cfb commit d128173
Show file tree
Hide file tree
Showing 50 changed files with 1,738 additions and 506 deletions.
21 changes: 12 additions & 9 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui

var annotations = model.GetAnnotations().ToList();

IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction);
IgnoreAnnotations(
annotations,
ChangeDetector.SkipDetectChangesAnnotation,
CoreAnnotationNames.ChangeTrackingStrategy,
CoreAnnotationNames.OwnedTypes,
RelationalAnnotationNames.CheckConstraints,
RelationalAnnotationNames.Tables);

if (annotations.Count > 0)
{
stringBuilder.Append(builderName);
Expand All @@ -65,14 +74,6 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
ref annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema),
stringBuilder);

IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction);
IgnoreAnnotations(
annotations,
ChangeDetector.SkipDetectChangesAnnotation,
CoreAnnotationNames.ChangeTrackingStrategy,
CoreAnnotationNames.OwnedTypes,
RelationalAnnotationNames.CheckConstraints);

GenerateAnnotations(annotations, stringBuilder);
}

Expand Down Expand Up @@ -495,6 +496,7 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property,
IgnoreAnnotations(
annotations,
RelationalAnnotationNames.ColumnType,
RelationalAnnotationNames.ColumnMappings,
CoreAnnotationNames.ValueGeneratorFactory,
CoreAnnotationNames.PropertyAccessMode,
CoreAnnotationNames.ChangeTrackingStrategy,
Expand Down Expand Up @@ -784,7 +786,8 @@ protected virtual void GenerateEntityTypeAnnotations(
CoreAnnotationNames.ConstructorBinding,
CoreAnnotationNames.DefiningQuery,
CoreAnnotationNames.QueryFilter,
RelationalAnnotationNames.CheckConstraints);
RelationalAnnotationNames.CheckConstraints,
RelationalAnnotationNames.TableMappings);

if (annotations.Count > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,10 @@ private IEnumerable<string> GetAnnotationNamespaces(IEnumerable<IAnnotatable> it
CoreAnnotationNames.ValueGeneratorFactory,
CoreAnnotationNames.DefiningQuery,
CoreAnnotationNames.QueryFilter,
RelationalAnnotationNames.CheckConstraints
RelationalAnnotationNames.CheckConstraints,
RelationalAnnotationNames.Tables,
RelationalAnnotationNames.TableMappings,
RelationalAnnotationNames.ColumnMappings
};

var ignoredAnnotationTypes = new List<string>
Expand Down
17 changes: 16 additions & 1 deletion src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Migrations.Internal
Expand All @@ -23,6 +25,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Internal
public class SnapshotModelProcessor : ISnapshotModelProcessor
{
private readonly IOperationReporter _operationReporter;
private readonly ProviderConventionSetBuilderDependencies _conventionDependencies;
private readonly HashSet<string> _relationalNames;

/// <summary>
Expand All @@ -31,9 +34,12 @@ public class SnapshotModelProcessor : ISnapshotModelProcessor
/// 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 SnapshotModelProcessor([NotNull] IOperationReporter operationReporter)
public SnapshotModelProcessor(
[NotNull] IOperationReporter operationReporter,
[NotNull] ProviderConventionSetBuilderDependencies conventionDependencies)
{
_operationReporter = operationReporter;
_conventionDependencies = conventionDependencies;
_relationalNames = new HashSet<string>(
typeof(RelationalAnnotationNames)
.GetRuntimeFields()
Expand Down Expand Up @@ -75,6 +81,15 @@ public virtual IModel Process(IModel model)
}
}

if (model is IConventionModel conventionModel
&& _conventionDependencies != null)
{
var typeMappingConvention = new TypeMappingConvention(_conventionDependencies);
typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null);

model = new RelationalModelConvention().ProcessModelFinalized(conventionModel);
}

return model is IMutableModel mutableModel
? mutableModel.FinalizeModel()
: model;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ protected virtual void GenerateOnModelCreating(
RemoveAnnotation(ref annotations, ChangeDetector.SkipDetectChangesAnnotation);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.MaxIdentifierLength);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.CheckConstraints);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Tables);
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DatabaseName);
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.EntityTypeErrors);

Expand Down Expand Up @@ -364,9 +365,10 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Schema);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableMappings);
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DbSetName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewDefinition);

RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewDefinition);
var isView = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null;
if (!useDataAnnotations || isView)
{
Expand Down Expand Up @@ -603,16 +605,17 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)

var annotations = property.GetAnnotations().ToList();

RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnType);
RemoveAnnotation(ref annotations, CoreAnnotationNames.MaxLength);
RemoveAnnotation(ref annotations, CoreAnnotationNames.TypeMapping);
RemoveAnnotation(ref annotations, CoreAnnotationNames.Unicode);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnType);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValue);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValueSql);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ComputedColumnSql);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.IsFixedLength);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnMappings);
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.ColumnOrdinal);

if (!useDataAnnotations)
Expand Down
40 changes: 40 additions & 0 deletions src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public static string GetTableName([NotNull] this IEntityType entityType) =>
/// <returns> The default name of the table to which the entity type would be mapped. </returns>
public static string GetDefaultTableName([NotNull] this IEntityType entityType)
{
if (entityType.GetDefiningQuery() != null)
{
return null;
}

var ownership = entityType.FindOwnership();
if (ownership != null
&& ownership.IsUnique)
Expand Down Expand Up @@ -140,6 +145,41 @@ public static void SetSchema(
=> entityType.FindAnnotation(RelationalAnnotationNames.Schema)
?.GetConfigurationSource();

/// <summary>
/// Returns the name of the table to which the entity type is mapped.
/// </summary>
/// <param name="entityType"> The entity type to get the table name for. </param>
/// <returns> The name of the table to which the entity type is mapped. </returns>
public static IEnumerable<ITableMapping> GetTableMappings([NotNull] this IEntityType entityType) =>
(IEnumerable<ITableMapping>)entityType[RelationalAnnotationNames.TableMappings];

/// <summary>
/// Returns the name of the view to which the entity type is mapped.
/// </summary>
/// <param name="entityType"> The entity type to get the view name for. </param>
/// <returns> The name of the view to which the entity type is mapped. </returns>
public static string GetViewName([NotNull] this IEntityType entityType)
{
if (entityType.BaseType != null)
{
return entityType.GetRootType().GetViewName();
}

if (entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null)
{
return entityType.GetTableName();
}

var ownership = entityType.FindOwnership();
if (ownership != null
&& ownership.IsUnique)
{
return ownership.PrincipalEntityType.GetViewName();
}

return null;
}

/// <summary>
/// Finds an <see cref="ICheckConstraint" /> with the given name.
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions src/EFCore.Relational/Extensions/RelationalModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@ public static void SetDefaultSchema(
public static ConfigurationSource? GetDefaultSchemaConfigurationSource([NotNull] this IConventionModel model)
=> model.FindAnnotation(RelationalAnnotationNames.DefaultSchema)?.GetConfigurationSource();

/// <summary>
/// Returns all the tables mapped in the model.
/// </summary>
/// <param name="model"> The model to get the tables for. </param>
/// <returns> All the tables mapped in the model. </returns>
public static IEnumerable<ITable> GetTables([NotNull] this IModel model) =>
((IDictionary<(string, string), Table>)model[RelationalAnnotationNames.Tables]).Values;

/// <summary>
/// Gets the table with a given name. Returns <c>null</c> if no table with the given name is defined.
/// </summary>
/// <param name="model"> The model to get the table for. </param>
/// <param name="name"> The name of the table. </param>
/// <param name="schema"> The schema of the table. </param>
/// <returns> The table with a given name or <c>null</c> if no table with the given name is defined. </returns>
public static ITable FindTable([NotNull] this IModel model, [NotNull] string name, [CanBeNull] string schema) =>
((IDictionary<(string, string), Table>)model[RelationalAnnotationNames.Tables]).TryGetValue((name, schema), out var table)
? table
: null;

/// <summary>
/// Returns the maximum length allowed for store identifiers.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ public static void SetColumnType(
public static ConfigurationSource? GetColumnTypeConfigurationSource([NotNull] this IConventionProperty property)
=> property.FindAnnotation(RelationalAnnotationNames.ColumnType)?.GetConfigurationSource();

/// <summary>
/// Returns the columns to which the property is mapped.
/// </summary>
/// <param name="property"> The property. </param>
/// <returns> The name of the table to which the entity type is mapped. </returns>
public static IEnumerable<IColumnMapping> GetColumnMappings([NotNull] this IProperty property) =>
(IEnumerable<IColumnMapping>)property[RelationalAnnotationNames.ColumnMappings];

/// <summary>
/// Returns the SQL expression that is used as the default value for the column this property is mapped to.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,13 @@ protected virtual void ValidateSharedTableCompatibility(
var tables = new Dictionary<string, List<IEntityType>>();
foreach (var entityType in model.GetEntityTypes())
{
var tableName = Format(entityType.GetSchema(), entityType.GetTableName());
var name = entityType.GetTableName();
if (name == null)
{
continue;
}

var tableName = Format(entityType.GetSchema(), name);

if (!tables.TryGetValue(tableName, out var mappedTypes))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public override ConventionSet CreateConventionSet()
(QueryFilterDefiningQueryRewritingConvention)new RelationalQueryFilterDefiningQueryRewritingConvention(
Dependencies, RelationalDependencies));

ConventionSet.AddAfter(
conventionSet.ModelFinalizedConventions,
new RelationalModelConvention(),
typeof(ValidatingConvention));

return conventionSet;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// A convention that precomputes a relational model.
/// </summary>
public class RelationalModelConvention : IModelFinalizedConvention
{
/// <inheritdoc />
public virtual IModel ProcessModelFinalized(IModel model)
=> model is IConventionModel conventionModel ? RelationalModel.AddRelationalModel(conventionModel) : model;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ private static void TryUniquifyTableNames(
foreach (var entityType in model.GetEntityTypes())
{
var tableName = (Schema: entityType.GetSchema(), TableName: entityType.GetTableName());
if (tableName.TableName == null)
{
continue;
}

if (!tables.TryGetValue(tableName, out var entityTypes))
{
Expand Down
23 changes: 23 additions & 0 deletions src/EFCore.Relational/Metadata/IColumn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Represents a column in a table.
/// </summary>
public interface IColumn : IColumnBase
{
/// <summary>
/// The containing table.
/// </summary>
new ITable Table { get; }

/// <summary>
/// The property mappings.
/// </summary>
new IEnumerable<IColumnMapping> PropertyMappings { get; }
}
}
40 changes: 40 additions & 0 deletions src/EFCore.Relational/Metadata/IColumnBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Represents a column-like object in a table-like object.
/// </summary>
public interface IColumnBase : IAnnotatable
{
/// <summary>
/// The column name.
/// </summary>
string Name { get; }

/// <summary>
/// The column type.
/// </summary>
string Type { get; }

/// <summary>
/// Whether the column can contain NULL.
/// </summary>
bool IsNullable { get; }

/// <summary>
/// The containing table-like object.
/// </summary>
ITableBase Table { get; }

/// <summary>
/// The property mappings.
/// </summary>
IEnumerable<IColumnMappingBase> PropertyMappings { get; }
}
}
21 changes: 21 additions & 0 deletions src/EFCore.Relational/Metadata/IColumnMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Represents property mapping to a column.
/// </summary>
public interface IColumnMapping : IColumnMappingBase
{
/// <summary>
/// The target column.
/// </summary>
new IColumn Column { get; }

/// <summary>
/// The containing table mapping.
/// </summary>
new ITableMapping TableMapping { get; }
}
}
Loading

0 comments on commit d128173

Please sign in to comment.