Skip to content

Commit f7f0d9a

Browse files
committed
Add a relational model API
Use the new model in migrations and update pipeline Part of #12846, #2725, #8258, #15671, #17270
1 parent 812e241 commit f7f0d9a

File tree

50 files changed

+1812
-506
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1812
-506
lines changed

src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
5555

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

58+
IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction);
59+
IgnoreAnnotations(
60+
annotations,
61+
ChangeDetector.SkipDetectChangesAnnotation,
62+
CoreAnnotationNames.ChangeTrackingStrategy,
63+
CoreAnnotationNames.OwnedTypes,
64+
RelationalAnnotationNames.CheckConstraints,
65+
RelationalAnnotationNames.Tables);
66+
5867
if (annotations.Count > 0)
5968
{
6069
stringBuilder.Append(builderName);
@@ -65,14 +74,6 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
6574
ref annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema),
6675
stringBuilder);
6776

68-
IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction);
69-
IgnoreAnnotations(
70-
annotations,
71-
ChangeDetector.SkipDetectChangesAnnotation,
72-
CoreAnnotationNames.ChangeTrackingStrategy,
73-
CoreAnnotationNames.OwnedTypes,
74-
RelationalAnnotationNames.CheckConstraints);
75-
7677
GenerateAnnotations(annotations, stringBuilder);
7778
}
7879

@@ -495,6 +496,7 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property,
495496
IgnoreAnnotations(
496497
annotations,
497498
RelationalAnnotationNames.ColumnType,
499+
RelationalAnnotationNames.TableColumnMappings,
498500
CoreAnnotationNames.ValueGeneratorFactory,
499501
CoreAnnotationNames.PropertyAccessMode,
500502
CoreAnnotationNames.ChangeTrackingStrategy,
@@ -784,7 +786,8 @@ protected virtual void GenerateEntityTypeAnnotations(
784786
CoreAnnotationNames.ConstructorBinding,
785787
CoreAnnotationNames.DefiningQuery,
786788
CoreAnnotationNames.QueryFilter,
787-
RelationalAnnotationNames.CheckConstraints);
789+
RelationalAnnotationNames.CheckConstraints,
790+
RelationalAnnotationNames.TableMappings);
788791

789792
if (annotations.Count > 0)
790793
{

src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ private IEnumerable<string> GetAnnotationNamespaces(IEnumerable<IAnnotatable> it
241241
CoreAnnotationNames.ValueGeneratorFactory,
242242
CoreAnnotationNames.DefiningQuery,
243243
CoreAnnotationNames.QueryFilter,
244-
RelationalAnnotationNames.CheckConstraints
244+
RelationalAnnotationNames.CheckConstraints,
245+
RelationalAnnotationNames.Tables,
246+
RelationalAnnotationNames.TableMappings,
247+
RelationalAnnotationNames.TableColumnMappings
245248
};
246249

247250
var ignoredAnnotationTypes = new List<string>

src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
using Microsoft.EntityFrameworkCore.Infrastructure;
1111
using Microsoft.EntityFrameworkCore.Internal;
1212
using Microsoft.EntityFrameworkCore.Metadata;
13+
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
14+
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
1315
using Microsoft.EntityFrameworkCore.Metadata.Internal;
1416

1517
namespace Microsoft.EntityFrameworkCore.Migrations.Internal
@@ -23,6 +25,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Internal
2325
public class SnapshotModelProcessor : ISnapshotModelProcessor
2426
{
2527
private readonly IOperationReporter _operationReporter;
28+
private readonly ProviderConventionSetBuilderDependencies _conventionDependencies;
2629
private readonly HashSet<string> _relationalNames;
2730

2831
/// <summary>
@@ -31,9 +34,12 @@ public class SnapshotModelProcessor : ISnapshotModelProcessor
3134
/// any release. You should only use it directly in your code with extreme caution and knowing that
3235
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3336
/// </summary>
34-
public SnapshotModelProcessor([NotNull] IOperationReporter operationReporter)
37+
public SnapshotModelProcessor(
38+
[NotNull] IOperationReporter operationReporter,
39+
[NotNull] ProviderConventionSetBuilderDependencies conventionDependencies)
3540
{
3641
_operationReporter = operationReporter;
42+
_conventionDependencies = conventionDependencies;
3743
_relationalNames = new HashSet<string>(
3844
typeof(RelationalAnnotationNames)
3945
.GetRuntimeFields()
@@ -75,6 +81,15 @@ public virtual IModel Process(IModel model)
7581
}
7682
}
7783

84+
if (model is IConventionModel conventionModel
85+
&& _conventionDependencies != null)
86+
{
87+
var typeMappingConvention = new TypeMappingConvention(_conventionDependencies);
88+
typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null);
89+
90+
model = new RelationalModelConvention().ProcessModelFinalized(conventionModel);
91+
}
92+
7893
return model is IMutableModel mutableModel
7994
? mutableModel.FinalizeModel()
8095
: model;

src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ protected virtual void GenerateOnModelCreating(
263263
RemoveAnnotation(ref annotations, ChangeDetector.SkipDetectChangesAnnotation);
264264
RemoveAnnotation(ref annotations, RelationalAnnotationNames.MaxIdentifierLength);
265265
RemoveAnnotation(ref annotations, RelationalAnnotationNames.CheckConstraints);
266+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Tables);
266267
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DatabaseName);
267268
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.EntityTypeErrors);
268269

@@ -364,9 +365,10 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)
364365
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableName);
365366
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
366367
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Schema);
368+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableMappings);
367369
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DbSetName);
368-
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewDefinition);
369370

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

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

606-
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName);
607-
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnType);
608608
RemoveAnnotation(ref annotations, CoreAnnotationNames.MaxLength);
609609
RemoveAnnotation(ref annotations, CoreAnnotationNames.TypeMapping);
610610
RemoveAnnotation(ref annotations, CoreAnnotationNames.Unicode);
611+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName);
612+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnType);
611613
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValue);
612614
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValueSql);
613615
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
614616
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ComputedColumnSql);
615617
RemoveAnnotation(ref annotations, RelationalAnnotationNames.IsFixedLength);
618+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableColumnMappings);
616619
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.ColumnOrdinal);
617620

618621
if (!useDataAnnotations)

src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public static string GetTableName([NotNull] this IEntityType entityType) =>
3333
/// <returns> The default name of the table to which the entity type would be mapped. </returns>
3434
public static string GetDefaultTableName([NotNull] this IEntityType entityType)
3535
{
36+
if (entityType.GetDefiningQuery() != null)
37+
{
38+
return null;
39+
}
40+
3641
var ownership = entityType.FindOwnership();
3742
if (ownership != null
3843
&& ownership.IsUnique)
@@ -140,6 +145,41 @@ public static void SetSchema(
140145
=> entityType.FindAnnotation(RelationalAnnotationNames.Schema)
141146
?.GetConfigurationSource();
142147

148+
/// <summary>
149+
/// Returns the name of the table to which the entity type is mapped.
150+
/// </summary>
151+
/// <param name="entityType"> The entity type to get the table name for. </param>
152+
/// <returns> The name of the table to which the entity type is mapped. </returns>
153+
public static IEnumerable<ITableMapping> GetTableMappings([NotNull] this IEntityType entityType) =>
154+
(IEnumerable<ITableMapping>)entityType[RelationalAnnotationNames.TableMappings];
155+
156+
/// <summary>
157+
/// Returns the name of the view to which the entity type is mapped.
158+
/// </summary>
159+
/// <param name="entityType"> The entity type to get the view name for. </param>
160+
/// <returns> The name of the view to which the entity type is mapped. </returns>
161+
public static string GetViewName([NotNull] this IEntityType entityType)
162+
{
163+
if (entityType.BaseType != null)
164+
{
165+
return entityType.GetRootType().GetViewName();
166+
}
167+
168+
if (entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null)
169+
{
170+
return entityType.GetTableName();
171+
}
172+
173+
var ownership = entityType.FindOwnership();
174+
if (ownership != null
175+
&& ownership.IsUnique)
176+
{
177+
return ownership.PrincipalEntityType.GetViewName();
178+
}
179+
180+
return null;
181+
}
182+
143183
/// <summary>
144184
/// Finds an <see cref="ICheckConstraint" /> with the given name.
145185
/// </summary>

src/EFCore.Relational/Extensions/RelationalModelExtensions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@ public static void SetDefaultSchema(
5555
public static ConfigurationSource? GetDefaultSchemaConfigurationSource([NotNull] this IConventionModel model)
5656
=> model.FindAnnotation(RelationalAnnotationNames.DefaultSchema)?.GetConfigurationSource();
5757

58+
/// <summary>
59+
/// Returns all the tables mapped in the model.
60+
/// </summary>
61+
/// <param name="model"> The model to get the tables for. </param>
62+
/// <returns> All the tables mapped in the model. </returns>
63+
public static IEnumerable<ITable> GetTables([NotNull] this IModel model) =>
64+
((IDictionary<(string, string), Table>)model[RelationalAnnotationNames.Tables]).Values;
65+
66+
/// <summary>
67+
/// Gets the table with a given name. Returns <c>null</c> if no table with the given name is defined.
68+
/// </summary>
69+
/// <param name="model"> The model to get the table for. </param>
70+
/// <param name="name"> The name of the table. </param>
71+
/// <param name="schema"> The schema of the table. </param>
72+
/// <returns> The table with a given name or <c>null</c> if no table with the given name is defined. </returns>
73+
public static ITable FindTable([NotNull] this IModel model, [NotNull] string name, [CanBeNull] string schema) =>
74+
((IDictionary<(string, string), Table>)model[RelationalAnnotationNames.Tables]).TryGetValue((name, schema), out var table)
75+
? table
76+
: null;
77+
5878
/// <summary>
5979
/// Returns the maximum length allowed for store identifiers.
6080
/// </summary>

src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ public static void SetColumnType(
174174
public static ConfigurationSource? GetColumnTypeConfigurationSource([NotNull] this IConventionProperty property)
175175
=> property.FindAnnotation(RelationalAnnotationNames.ColumnType)?.GetConfigurationSource();
176176

177+
/// <summary>
178+
/// Returns the columns to which the property is mapped.
179+
/// </summary>
180+
/// <param name="property"> The property. </param>
181+
/// <returns> The name of the table to which the entity type is mapped. </returns>
182+
public static IEnumerable<IColumnMapping> GetTableColumnMappings([NotNull] this IProperty property) =>
183+
(IEnumerable<IColumnMapping>)property[RelationalAnnotationNames.TableColumnMappings];
184+
177185
/// <summary>
178186
/// Returns the SQL expression that is used as the default value for the column this property is mapped to.
179187
/// </summary>

src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,13 @@ protected virtual void ValidateSharedTableCompatibility(
156156
var tables = new Dictionary<string, List<IEntityType>>();
157157
foreach (var entityType in model.GetEntityTypes())
158158
{
159-
var tableName = Format(entityType.GetSchema(), entityType.GetTableName());
159+
var name = entityType.GetTableName();
160+
if (name == null)
161+
{
162+
continue;
163+
}
164+
165+
var tableName = Format(entityType.GetSchema(), name);
160166

161167
if (!tables.TryGetValue(tableName, out var mappedTypes))
162168
{

src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public override ConventionSet CreateConventionSet()
9797
(QueryFilterDefiningQueryRewritingConvention)new RelationalQueryFilterDefiningQueryRewritingConvention(
9898
Dependencies, RelationalDependencies));
9999

100+
ConventionSet.AddAfter(
101+
conventionSet.ModelFinalizedConventions,
102+
new RelationalModelConvention(),
103+
typeof(ValidatingConvention));
104+
100105
return conventionSet;
101106
}
102107
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
5+
6+
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
7+
{
8+
/// <summary>
9+
/// A convention that precomputes a relational model.
10+
/// </summary>
11+
public class RelationalModelConvention : IModelFinalizedConvention
12+
{
13+
/// <inheritdoc />
14+
public virtual IModel ProcessModelFinalized(IModel model)
15+
=> model is IConventionModel conventionModel ? RelationalModel.AddRelationalModel(conventionModel) : model;
16+
}
17+
}

src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ private static void TryUniquifyTableNames(
6969
foreach (var entityType in model.GetEntityTypes())
7070
{
7171
var tableName = (Schema: entityType.GetSchema(), TableName: entityType.GetTableName());
72+
if (tableName.TableName == null)
73+
{
74+
continue;
75+
}
7276

7377
if (!tables.TryGetValue(tableName, out var entityTypes))
7478
{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
6+
namespace Microsoft.EntityFrameworkCore.Metadata
7+
{
8+
/// <summary>
9+
/// Represents a column in a table.
10+
/// </summary>
11+
public interface IColumn : IColumnBase
12+
{
13+
/// <summary>
14+
/// The containing table.
15+
/// </summary>
16+
new ITable Table { get; }
17+
18+
/// <summary>
19+
/// The property mappings.
20+
/// </summary>
21+
new IEnumerable<IColumnMapping> PropertyMappings { get; }
22+
}
23+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using Microsoft.EntityFrameworkCore.Infrastructure;
7+
8+
namespace Microsoft.EntityFrameworkCore.Metadata
9+
{
10+
/// <summary>
11+
/// Represents a column-like object in a table-like object.
12+
/// </summary>
13+
public interface IColumnBase : IAnnotatable
14+
{
15+
/// <summary>
16+
/// The column name.
17+
/// </summary>
18+
string Name { get; }
19+
20+
/// <summary>
21+
/// The column type.
22+
/// </summary>
23+
string Type { get; }
24+
25+
/// <summary>
26+
/// Whether the column can contain NULL.
27+
/// </summary>
28+
bool IsNullable { get; }
29+
30+
/// <summary>
31+
/// The containing table-like object.
32+
/// </summary>
33+
ITableBase Table { get; }
34+
35+
/// <summary>
36+
/// The property mappings.
37+
/// </summary>
38+
IEnumerable<IColumnMappingBase> PropertyMappings { get; }
39+
}
40+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata
5+
{
6+
/// <summary>
7+
/// Represents property mapping to a column.
8+
/// </summary>
9+
public interface IColumnMapping : IColumnMappingBase
10+
{
11+
/// <summary>
12+
/// The target column.
13+
/// </summary>
14+
new IColumn Column { get; }
15+
16+
/// <summary>
17+
/// The containing table mapping.
18+
/// </summary>
19+
new ITableMapping TableMapping { get; }
20+
}
21+
}

0 commit comments

Comments
 (0)