Skip to content

Commit 1cce838

Browse files
committed
Allow to map an entity type to both a table and a view.
Fixes #17270 Fixes #15671
1 parent 23f9bde commit 1cce838

24 files changed

+678
-153
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,12 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property,
438438
nameof(RelationalPropertyBuilderExtensions.HasColumnName),
439439
stringBuilder);
440440

441+
GenerateFluentApiForAnnotation(
442+
ref annotations,
443+
RelationalAnnotationNames.ViewColumnName,
444+
nameof(RelationalPropertyBuilderExtensions.HasViewColumnName),
445+
stringBuilder);
446+
441447
stringBuilder
442448
.AppendLine()
443449
.Append(".")

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

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -364,15 +364,16 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)
364364
var annotations = entityType.GetAnnotations().ToList();
365365
RemoveAnnotation(ref annotations, CoreAnnotationNames.ConstructorBinding);
366366
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableName);
367-
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
368367
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Schema);
368+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewName);
369+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewSchema);
369370
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableMappings);
370371
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewMappings);
371372
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DbSetName);
372-
373+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
373374
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewDefinition);
374-
var isView = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null;
375-
if (!useDataAnnotations || isView)
375+
376+
if (!useDataAnnotations || entityType.GetViewName() != null)
376377
{
377378
GenerateTableName(entityType);
378379
}
@@ -533,9 +534,7 @@ private void GenerateTableName(IEntityType entityType)
533534

534535
var explicitSchema = schema != null && schema != defaultSchema;
535536
var explicitTable = explicitSchema || tableName != null && tableName != entityType.GetDbSetName();
536-
537-
var isView = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null;
538-
if (explicitTable || isView)
537+
if (explicitTable)
539538
{
540539
var parameterString = _code.Literal(tableName);
541540
if (explicitSchema)
@@ -545,7 +544,29 @@ private void GenerateTableName(IEntityType entityType)
545544

546545
var lines = new List<string>
547546
{
548-
$".{(isView ? nameof(RelationalEntityTypeBuilderExtensions.ToView) : nameof(RelationalEntityTypeBuilderExtensions.ToTable))}({parameterString})"
547+
$".{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}({parameterString})"
548+
};
549+
550+
AppendMultiLineFluentApi(entityType, lines);
551+
}
552+
553+
var viewName = entityType.GetViewName();
554+
var viewSchema = entityType.GetViewSchema();
555+
556+
var explicitViewSchema = viewSchema != null && viewSchema != defaultSchema;
557+
var explicitViewTable = explicitViewSchema || viewName != null;
558+
559+
if (explicitViewTable)
560+
{
561+
var parameterString = _code.Literal(viewName);
562+
if (explicitViewSchema)
563+
{
564+
parameterString += ", " + _code.Literal(viewSchema);
565+
}
566+
567+
var lines = new List<string>
568+
{
569+
$".{nameof(RelationalEntityTypeBuilderExtensions.ToView)}({parameterString})"
549570
};
550571

551572
AppendMultiLineFluentApi(entityType, lines);
@@ -614,6 +635,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
614635
RemoveAnnotation(ref annotations, CoreAnnotationNames.TypeMapping);
615636
RemoveAnnotation(ref annotations, CoreAnnotationNames.Unicode);
616637
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName);
638+
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewColumnName);
617639
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnType);
618640
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValue);
619641
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValueSql);
@@ -643,6 +665,16 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
643665
$"({_code.Literal(columnName)})");
644666
}
645667

668+
var viewColumnName = property.GetViewColumnName();
669+
670+
if (viewColumnName != null
671+
&& viewColumnName != columnName)
672+
{
673+
lines.Add(
674+
$".{nameof(RelationalPropertyBuilderExtensions.HasViewColumnName)}" +
675+
$"({_code.Literal(columnName)})");
676+
}
677+
646678
var columnType = property.GetConfiguredColumnType();
647679

648680
if (columnType != null)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,8 @@ private void GenerateTableAttribute(IEntityType entityType)
152152
var defaultSchema = entityType.Model.GetDefaultSchema();
153153

154154
var schemaParameterNeeded = schema != null && schema != defaultSchema;
155-
var isView = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null;
155+
var isView = entityType.GetViewName() != null;
156156
var tableAttributeNeeded = !isView && (schemaParameterNeeded || tableName != null && tableName != entityType.GetDbSetName());
157-
158157
if (tableAttributeNeeded)
159158
{
160159
var tableAttribute = new AttributeWriter(nameof(TableAttribute));

src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public static EntityTypeBuilder ToView(
277277
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
278278
Check.NullButNotEmpty(name, nameof(name));
279279

280-
entityTypeBuilder.Metadata.SetTableName(name);
280+
entityTypeBuilder.Metadata.SetViewName(name);
281281
entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinition, null);
282282

283283
return entityTypeBuilder;
@@ -312,8 +312,8 @@ public static EntityTypeBuilder ToView(
312312
Check.NullButNotEmpty(name, nameof(name));
313313
Check.NullButNotEmpty(schema, nameof(schema));
314314

315-
entityTypeBuilder.Metadata.SetTableName(name);
316-
entityTypeBuilder.Metadata.SetSchema(schema);
315+
entityTypeBuilder.Metadata.SetViewName(name);
316+
entityTypeBuilder.Metadata.SetViewSchema(schema);
317317
entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinition, null);
318318

319319
return entityTypeBuilder;

src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs

Lines changed: 134 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using JetBrains.Annotations;
67
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -21,10 +22,24 @@ public static class RelationalEntityTypeExtensions
2122
/// </summary>
2223
/// <param name="entityType"> The entity type to get the table name for. </param>
2324
/// <returns> The name of the table to which the entity type is mapped. </returns>
24-
public static string GetTableName([NotNull] this IEntityType entityType) =>
25-
entityType.BaseType != null
26-
? entityType.GetRootType().GetTableName()
27-
: (string)entityType[RelationalAnnotationNames.TableName] ?? GetDefaultTableName(entityType);
25+
public static string GetTableName([NotNull] this IEntityType entityType)
26+
{
27+
if (entityType.BaseType != null)
28+
{
29+
return entityType.GetRootType().GetTableName();
30+
}
31+
32+
var nameAnnotation = entityType.FindAnnotation(RelationalAnnotationNames.TableName);
33+
if (nameAnnotation != null)
34+
{
35+
return (string)nameAnnotation.Value;
36+
}
37+
38+
return ((entityType as IConventionEntityType)?.GetViewNameConfigurationSource() == null
39+
&& ((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null)
40+
? GetDefaultTableName(entityType)
41+
: null);
42+
}
2843

2944
/// <summary>
3045
/// Returns the default table name that would be used for this entity type.
@@ -33,23 +48,23 @@ public static string GetTableName([NotNull] this IEntityType entityType) =>
3348
/// <returns> The default name of the table to which the entity type would be mapped. </returns>
3449
public static string GetDefaultTableName([NotNull] this IEntityType entityType)
3550
{
36-
if (entityType.GetDefiningQuery() != null)
37-
{
38-
return null;
39-
}
40-
4151
var ownership = entityType.FindOwnership();
4252
if (ownership != null
4353
&& ownership.IsUnique)
4454
{
4555
return ownership.PrincipalEntityType.GetTableName();
4656
}
4757

48-
return Uniquifier.Truncate(
49-
entityType.HasDefiningNavigation()
50-
? $"{entityType.DefiningEntityType.GetTableName()}_{entityType.DefiningNavigationName}"
51-
: entityType.ShortName(),
52-
entityType.Model.GetMaxIdentifierLength());
58+
var name = entityType.ShortName();
59+
if (entityType.HasDefiningNavigation())
60+
{
61+
var definingTypeName = entityType.DefiningEntityType.GetTableName();
62+
name = definingTypeName != null
63+
? $"{definingTypeName}_{entityType.DefiningNavigationName}"
64+
: $"{entityType.DefiningNavigationName}_{name}";
65+
}
66+
67+
return Uniquifier.Truncate(name, entityType.Model.GetMaxIdentifierLength());
5368
}
5469

5570
/// <summary>
@@ -58,7 +73,7 @@ public static string GetDefaultTableName([NotNull] this IEntityType entityType)
5873
/// <param name="entityType"> The entity type to set the table name for. </param>
5974
/// <param name="name"> The name to set. </param>
6075
public static void SetTableName([NotNull] this IMutableEntityType entityType, [CanBeNull] string name)
61-
=> entityType.SetOrRemoveAnnotation(
76+
=> entityType.SetAnnotation(
6277
RelationalAnnotationNames.TableName,
6378
Check.NullButNotEmpty(name, nameof(name)));
6479

@@ -70,7 +85,7 @@ public static void SetTableName([NotNull] this IMutableEntityType entityType, [C
7085
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
7186
public static void SetTableName(
7287
[NotNull] this IConventionEntityType entityType, [CanBeNull] string name, bool fromDataAnnotation = false)
73-
=> entityType.SetOrRemoveAnnotation(
88+
=> entityType.SetAnnotation(
7489
RelationalAnnotationNames.TableName,
7590
Check.NullButNotEmpty(name, nameof(name)),
7691
fromDataAnnotation);
@@ -97,7 +112,7 @@ public static string GetSchema([NotNull] this IEntityType entityType) =>
97112
/// <summary>
98113
/// Returns the default database schema that would be used for this entity type.
99114
/// </summary>
100-
/// <param name="entityType"> The entity type to get the table name for. </param>
115+
/// <param name="entityType"> The entity type to get the table schema for. </param>
101116
/// <returns> The default database schema to which the entity type would be mapped. </returns>
102117
public static string GetDefaultSchema([NotNull] this IEntityType entityType)
103118
{
@@ -109,7 +124,7 @@ public static string GetDefaultSchema([NotNull] this IEntityType entityType)
109124
}
110125

111126
return entityType.HasDefiningNavigation()
112-
? entityType.DefiningEntityType.GetSchema()
127+
? entityType.DefiningEntityType.GetSchema() ?? entityType.Model.GetDefaultSchema()
113128
: entityType.Model.GetDefaultSchema();
114129
}
115130

@@ -174,28 +189,119 @@ public static IEnumerable<IViewMapping> GetViewMappings([NotNull] this IEntityTy
174189
/// </summary>
175190
/// <param name="entityType"> The entity type to get the view name for. </param>
176191
/// <returns> The name of the view to which the entity type is mapped. </returns>
177-
public static string GetViewName([NotNull] this IEntityType entityType)
192+
public static string GetViewName([NotNull] this IEntityType entityType) =>
193+
entityType.BaseType != null
194+
? entityType.GetRootType().GetViewName()
195+
: (string)entityType[RelationalAnnotationNames.ViewName]
196+
?? (((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null)
197+
? GetDefaultViewName(entityType)
198+
: null);
199+
200+
/// <summary>
201+
/// Returns the default view name that would be used for this entity type.
202+
/// </summary>
203+
/// <param name="entityType"> The entity type to get the table name for. </param>
204+
/// <returns> The default name of the table to which the entity type would be mapped. </returns>
205+
public static string GetDefaultViewName([NotNull] this IEntityType entityType)
178206
{
179-
if (entityType.BaseType != null)
180-
{
181-
return entityType.GetRootType().GetViewName();
182-
}
207+
var ownership = entityType.FindOwnership();
208+
return ownership != null
209+
&& ownership.IsUnique
210+
? ownership.PrincipalEntityType.GetViewName()
211+
: null;
212+
}
183213

184-
if (entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null)
185-
{
186-
return entityType.GetTableName();
187-
}
214+
/// <summary>
215+
/// Sets the name of the view to which the entity type is mapped.
216+
/// </summary>
217+
/// <param name="entityType"> The entity type to set the view name for. </param>
218+
/// <param name="name"> The name to set. </param>
219+
public static void SetViewName([NotNull] this IMutableEntityType entityType, [CanBeNull] string name)
220+
=> entityType.SetAnnotation(
221+
RelationalAnnotationNames.ViewName,
222+
Check.NullButNotEmpty(name, nameof(name)));
223+
224+
/// <summary>
225+
/// Sets the name of the view to which the entity type is mapped.
226+
/// </summary>
227+
/// <param name="entityType"> The entity type to set the view name for. </param>
228+
/// <param name="name"> The name to set. </param>
229+
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
230+
public static void SetViewName(
231+
[NotNull] this IConventionEntityType entityType, [CanBeNull] string name, bool fromDataAnnotation = false)
232+
=> entityType.SetAnnotation(
233+
RelationalAnnotationNames.ViewName,
234+
Check.NullButNotEmpty(name, nameof(name)),
235+
fromDataAnnotation);
236+
237+
/// <summary>
238+
/// Gets the <see cref="ConfigurationSource" /> for the view name.
239+
/// </summary>
240+
/// <param name="entityType"> The entity type to find configuration source for. </param>
241+
/// <returns> The <see cref="ConfigurationSource" /> for the view name. </returns>
242+
public static ConfigurationSource? GetViewNameConfigurationSource([NotNull] this IConventionEntityType entityType)
243+
=> entityType.FindAnnotation(RelationalAnnotationNames.ViewName)
244+
?.GetConfigurationSource();
245+
246+
/// <summary>
247+
/// Returns the database schema that contains the mapped view.
248+
/// </summary>
249+
/// <param name="entityType"> The entity type to get the view schema for. </param>
250+
/// <returns> The database schema that contains the mapped view. </returns>
251+
public static string GetViewSchema([NotNull] this IEntityType entityType) =>
252+
entityType.BaseType != null
253+
? entityType.GetRootType().GetViewSchema()
254+
: (string)entityType[RelationalAnnotationNames.ViewSchema] ?? GetDefaultViewSchema(entityType);
188255

256+
/// <summary>
257+
/// Returns the default database schema that would be used for this entity view.
258+
/// </summary>
259+
/// <param name="entityType"> The entity type to get the view schema for. </param>
260+
/// <returns> The default database schema to which the entity type would be mapped. </returns>
261+
public static string GetDefaultViewSchema([NotNull] this IEntityType entityType)
262+
{
189263
var ownership = entityType.FindOwnership();
190264
if (ownership != null
191265
&& ownership.IsUnique)
192266
{
193-
return ownership.PrincipalEntityType.GetViewName();
267+
return ownership.PrincipalEntityType.GetViewSchema();
194268
}
195269

196270
return null;
197271
}
198272

273+
/// <summary>
274+
/// Sets the database schema that contains the mapped view.
275+
/// </summary>
276+
/// <param name="entityType"> The entity type to set the view schema for. </param>
277+
/// <param name="value"> The value to set. </param>
278+
public static void SetViewSchema([NotNull] this IMutableEntityType entityType, [CanBeNull] string value)
279+
=> entityType.SetOrRemoveAnnotation(
280+
RelationalAnnotationNames.ViewSchema,
281+
Check.NullButNotEmpty(value, nameof(value)));
282+
283+
/// <summary>
284+
/// Sets the database schema that contains the mapped view.
285+
/// </summary>
286+
/// <param name="entityType"> The entity type to set the view schema for. </param>
287+
/// <param name="value"> The value to set. </param>
288+
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
289+
public static void SetViewSchema(
290+
[NotNull] this IConventionEntityType entityType, [CanBeNull] string value, bool fromDataAnnotation = false)
291+
=> entityType.SetOrRemoveAnnotation(
292+
RelationalAnnotationNames.ViewSchema,
293+
Check.NullButNotEmpty(value, nameof(value)),
294+
fromDataAnnotation);
295+
296+
/// <summary>
297+
/// Gets the <see cref="ConfigurationSource" /> for the view schema.
298+
/// </summary>
299+
/// <param name="entityType"> The entity type to find configuration source for. </param>
300+
/// <returns> The <see cref="ConfigurationSource" /> for the view schema. </returns>
301+
public static ConfigurationSource? GetViewSchemaConfigurationSource([NotNull] this IConventionEntityType entityType)
302+
=> entityType.FindAnnotation(RelationalAnnotationNames.ViewSchema)
303+
?.GetConfigurationSource();
304+
199305
/// <summary>
200306
/// Finds an <see cref="ICheckConstraint" /> with the given name.
201307
/// </summary>

0 commit comments

Comments
 (0)