Skip to content

Commit

Permalink
Allow to map an entity type to both a table and a view.
Browse files Browse the repository at this point in the history
Fixes #17270
Fixes #15671
  • Loading branch information
AndriySvyryd committed Feb 25, 2020
1 parent 23f9bde commit 35cc430
Show file tree
Hide file tree
Showing 24 changed files with 679 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,12 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property,
nameof(RelationalPropertyBuilderExtensions.HasColumnName),
stringBuilder);

GenerateFluentApiForAnnotation(
ref annotations,
RelationalAnnotationNames.ViewColumnName,
nameof(RelationalPropertyBuilderExtensions.HasViewColumnName),
stringBuilder);

stringBuilder
.AppendLine()
.Append(".")
Expand Down
48 changes: 40 additions & 8 deletions src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,16 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)
var annotations = entityType.GetAnnotations().ToList();
RemoveAnnotation(ref annotations, CoreAnnotationNames.ConstructorBinding);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Schema);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewSchema);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableMappings);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewMappings);
RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DbSetName);

RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewDefinition);
var isView = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null;
if (!useDataAnnotations || isView)

if (!useDataAnnotations || entityType.GetViewName() != null)
{
GenerateTableName(entityType);
}
Expand Down Expand Up @@ -533,9 +534,7 @@ private void GenerateTableName(IEntityType entityType)

var explicitSchema = schema != null && schema != defaultSchema;
var explicitTable = explicitSchema || tableName != null && tableName != entityType.GetDbSetName();

var isView = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null;
if (explicitTable || isView)
if (explicitTable)
{
var parameterString = _code.Literal(tableName);
if (explicitSchema)
Expand All @@ -545,7 +544,29 @@ private void GenerateTableName(IEntityType entityType)

var lines = new List<string>
{
$".{(isView ? nameof(RelationalEntityTypeBuilderExtensions.ToView) : nameof(RelationalEntityTypeBuilderExtensions.ToTable))}({parameterString})"
$".{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}({parameterString})"
};

AppendMultiLineFluentApi(entityType, lines);
}

var viewName = entityType.GetViewName();
var viewSchema = entityType.GetViewSchema();

var explicitViewSchema = viewSchema != null && viewSchema != defaultSchema;
var explicitViewTable = explicitViewSchema || viewName != null;

if (explicitViewTable)
{
var parameterString = _code.Literal(viewName);
if (explicitViewSchema)
{
parameterString += ", " + _code.Literal(viewSchema);
}

var lines = new List<string>
{
$".{nameof(RelationalEntityTypeBuilderExtensions.ToView)}({parameterString})"
};

AppendMultiLineFluentApi(entityType, lines);
Expand Down Expand Up @@ -614,6 +635,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
RemoveAnnotation(ref annotations, CoreAnnotationNames.TypeMapping);
RemoveAnnotation(ref annotations, CoreAnnotationNames.Unicode);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewColumnName);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnType);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValue);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValueSql);
Expand Down Expand Up @@ -643,6 +665,16 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
$"({_code.Literal(columnName)})");
}

var viewColumnName = property.GetViewColumnName();

if (viewColumnName != null
&& viewColumnName != columnName)
{
lines.Add(
$".{nameof(RelationalPropertyBuilderExtensions.HasViewColumnName)}" +
$"({_code.Literal(columnName)})");
}

var columnType = property.GetConfiguredColumnType();

if (columnType != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,8 @@ private void GenerateTableAttribute(IEntityType entityType)
var defaultSchema = entityType.Model.GetDefaultSchema();

var schemaParameterNeeded = schema != null && schema != defaultSchema;
var isView = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null;
var isView = entityType.GetViewName() != null;
var tableAttributeNeeded = !isView && (schemaParameterNeeded || tableName != null && tableName != entityType.GetDbSetName());

if (tableAttributeNeeded)
{
var tableAttribute = new AttributeWriter(nameof(TableAttribute));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public static EntityTypeBuilder ToView(
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NullButNotEmpty(name, nameof(name));

entityTypeBuilder.Metadata.SetTableName(name);
entityTypeBuilder.Metadata.SetViewName(name);
entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinition, null);

return entityTypeBuilder;
Expand Down Expand Up @@ -312,8 +312,8 @@ public static EntityTypeBuilder ToView(
Check.NullButNotEmpty(name, nameof(name));
Check.NullButNotEmpty(schema, nameof(schema));

entityTypeBuilder.Metadata.SetTableName(name);
entityTypeBuilder.Metadata.SetSchema(schema);
entityTypeBuilder.Metadata.SetViewName(name);
entityTypeBuilder.Metadata.SetViewSchema(schema);
entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinition, null);

return entityTypeBuilder;
Expand Down
162 changes: 134 additions & 28 deletions src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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;
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
Expand All @@ -21,10 +22,24 @@ public static class RelationalEntityTypeExtensions
/// </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 string GetTableName([NotNull] this IEntityType entityType) =>
entityType.BaseType != null
? entityType.GetRootType().GetTableName()
: (string)entityType[RelationalAnnotationNames.TableName] ?? GetDefaultTableName(entityType);
public static string GetTableName([NotNull] this IEntityType entityType)
{
if (entityType.BaseType != null)
{
return entityType.GetRootType().GetTableName();
}

var nameAnnotation = entityType.FindAnnotation(RelationalAnnotationNames.TableName);
if (nameAnnotation != null)
{
return (string)nameAnnotation.Value;
}

return ((entityType as IConventionEntityType)?.GetViewNameConfigurationSource() == null
&& ((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null)
? GetDefaultTableName(entityType)
: null);
}

/// <summary>
/// Returns the default table name that would be used for this entity type.
Expand All @@ -33,23 +48,23 @@ 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)
{
return ownership.PrincipalEntityType.GetTableName();
}

return Uniquifier.Truncate(
entityType.HasDefiningNavigation()
? $"{entityType.DefiningEntityType.GetTableName()}_{entityType.DefiningNavigationName}"
: entityType.ShortName(),
entityType.Model.GetMaxIdentifierLength());
var name = entityType.ShortName();
if (entityType.HasDefiningNavigation())
{
var definingTypeName = entityType.DefiningEntityType.GetTableName();
name = definingTypeName != null
? $"{definingTypeName}_{entityType.DefiningNavigationName}"
: $"{entityType.DefiningNavigationName}_{name}";
}

return Uniquifier.Truncate(name, entityType.Model.GetMaxIdentifierLength());
}

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

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

return entityType.HasDefiningNavigation()
? entityType.DefiningEntityType.GetSchema()
? entityType.DefiningEntityType.GetSchema() ?? entityType.Model.GetDefaultSchema()
: entityType.Model.GetDefaultSchema();
}

Expand Down Expand Up @@ -174,28 +189,119 @@ public static IEnumerable<IViewMapping> GetViewMappings([NotNull] this IEntityTy
/// </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)
public static string GetViewName([NotNull] this IEntityType entityType) =>
entityType.BaseType != null
? entityType.GetRootType().GetViewName()
: (string)entityType[RelationalAnnotationNames.ViewName]
?? (((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null)
? GetDefaultViewName(entityType)
: null);

/// <summary>
/// Returns the default view name that would be used for this entity type.
/// </summary>
/// <param name="entityType"> The entity type to get the table name for. </param>
/// <returns> The default name of the table to which the entity type would be mapped. </returns>
public static string GetDefaultViewName([NotNull] this IEntityType entityType)
{
if (entityType.BaseType != null)
{
return entityType.GetRootType().GetViewName();
}
var ownership = entityType.FindOwnership();
return ownership != null
&& ownership.IsUnique
? ownership.PrincipalEntityType.GetViewName()
: null;
}

if (entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition) != null)
{
return entityType.GetTableName();
}
/// <summary>
/// Sets the name of the view to which the entity type is mapped.
/// </summary>
/// <param name="entityType"> The entity type to set the view name for. </param>
/// <param name="name"> The name to set. </param>
public static void SetViewName([NotNull] this IMutableEntityType entityType, [CanBeNull] string name)
=> entityType.SetAnnotation(
RelationalAnnotationNames.ViewName,
Check.NullButNotEmpty(name, nameof(name)));

/// <summary>
/// Sets the name of the view to which the entity type is mapped.
/// </summary>
/// <param name="entityType"> The entity type to set the view name for. </param>
/// <param name="name"> The name to set. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
public static void SetViewName(
[NotNull] this IConventionEntityType entityType, [CanBeNull] string name, bool fromDataAnnotation = false)
=> entityType.SetAnnotation(
RelationalAnnotationNames.ViewName,
Check.NullButNotEmpty(name, nameof(name)),
fromDataAnnotation);

/// <summary>
/// Gets the <see cref="ConfigurationSource" /> for the view name.
/// </summary>
/// <param name="entityType"> The entity type to find configuration source for. </param>
/// <returns> The <see cref="ConfigurationSource" /> for the view name. </returns>
public static ConfigurationSource? GetViewNameConfigurationSource([NotNull] this IConventionEntityType entityType)
=> entityType.FindAnnotation(RelationalAnnotationNames.ViewName)
?.GetConfigurationSource();

/// <summary>
/// Returns the database schema that contains the mapped view.
/// </summary>
/// <param name="entityType"> The entity type to get the view schema for. </param>
/// <returns> The database schema that contains the mapped view. </returns>
public static string GetViewSchema([NotNull] this IEntityType entityType) =>
entityType.BaseType != null
? entityType.GetRootType().GetViewSchema()
: (string)entityType[RelationalAnnotationNames.ViewSchema] ?? GetDefaultViewSchema(entityType);

/// <summary>
/// Returns the default database schema that would be used for this entity view.
/// </summary>
/// <param name="entityType"> The entity type to get the view schema for. </param>
/// <returns> The default database schema to which the entity type would be mapped. </returns>
public static string GetDefaultViewSchema([NotNull] this IEntityType entityType)
{
var ownership = entityType.FindOwnership();
if (ownership != null
&& ownership.IsUnique)
{
return ownership.PrincipalEntityType.GetViewName();
return ownership.PrincipalEntityType.GetViewSchema();
}

return null;
}

/// <summary>
/// Sets the database schema that contains the mapped view.
/// </summary>
/// <param name="entityType"> The entity type to set the view schema for. </param>
/// <param name="value"> The value to set. </param>
public static void SetViewSchema([NotNull] this IMutableEntityType entityType, [CanBeNull] string value)
=> entityType.SetOrRemoveAnnotation(
RelationalAnnotationNames.ViewSchema,
Check.NullButNotEmpty(value, nameof(value)));

/// <summary>
/// Sets the database schema that contains the mapped view.
/// </summary>
/// <param name="entityType"> The entity type to set the view schema for. </param>
/// <param name="value"> The value to set. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
public static void SetViewSchema(
[NotNull] this IConventionEntityType entityType, [CanBeNull] string value, bool fromDataAnnotation = false)
=> entityType.SetOrRemoveAnnotation(
RelationalAnnotationNames.ViewSchema,
Check.NullButNotEmpty(value, nameof(value)),
fromDataAnnotation);

/// <summary>
/// Gets the <see cref="ConfigurationSource" /> for the view schema.
/// </summary>
/// <param name="entityType"> The entity type to find configuration source for. </param>
/// <returns> The <see cref="ConfigurationSource" /> for the view schema. </returns>
public static ConfigurationSource? GetViewSchemaConfigurationSource([NotNull] this IConventionEntityType entityType)
=> entityType.FindAnnotation(RelationalAnnotationNames.ViewSchema)
?.GetConfigurationSource();

/// <summary>
/// Finds an <see cref="ICheckConstraint" /> with the given name.
/// </summary>
Expand Down
Loading

0 comments on commit 35cc430

Please sign in to comment.