Skip to content

Commit

Permalink
Allow to map entity types to TVFs (#21473)
Browse files Browse the repository at this point in the history
Part of #20051
  • Loading branch information
AndriySvyryd committed Jul 3, 2020
1 parent 69071c1 commit 297130c
Show file tree
Hide file tree
Showing 59 changed files with 2,811 additions and 524 deletions.
21 changes: 21 additions & 0 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,27 @@ protected virtual void GenerateEntityTypeAnnotations(
}
}

var functionNameAnnotation = annotations.Find(RelationalAnnotationNames.FunctionName);
if (functionNameAnnotation?.Value != null
|| entityType.BaseType == null)
{
var functionName = (string)functionNameAnnotation?.Value ?? entityType.GetFunctionName();
if (functionName != null)
{
stringBuilder
.AppendLine()
.Append(builderName)
.Append(".ToFunction(")
.Append(Code.Literal(functionName));
if (functionNameAnnotation != null)
{
annotations.Remove(functionNameAnnotation.Name);
}

stringBuilder.AppendLine(");");
}
}

if ((discriminatorPropertyAnnotation?.Value
?? discriminatorMappingCompleteAnnotation?.Value
?? discriminatorValueAnnotation?.Value) != null)
Expand Down
16 changes: 15 additions & 1 deletion src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
RelationalAnnotationNames.TableColumnMappings,
RelationalAnnotationNames.ViewMappings,
RelationalAnnotationNames.ViewColumnMappings,
RelationalAnnotationNames.FunctionMappings,
RelationalAnnotationNames.FunctionColumnMappings,
RelationalAnnotationNames.ForeignKeyMappings,
RelationalAnnotationNames.TableIndexMappings,
RelationalAnnotationNames.UniqueConstraintMappings,
Expand Down Expand Up @@ -89,11 +91,18 @@ public virtual void RemoveAnnotationsHandledByConventions(

if (annotations.TryGetValue(RelationalAnnotationNames.ViewColumnName, out var viewColumnNameAnnotation)
&& viewColumnNameAnnotation.Value is string viewColumnName
&& viewColumnName != columnName)
&& viewColumnName == columnName)
{
annotations.Remove(RelationalAnnotationNames.ViewColumnName);
}

if (annotations.TryGetValue(RelationalAnnotationNames.FunctionColumnName, out var functionColumnNameAnnotation)
&& functionColumnNameAnnotation.Value is string functionColumnName
&& functionColumnName == columnName)
{
annotations.Remove(RelationalAnnotationNames.FunctionColumnName);
}

RemoveConventionalAnnotationsHelper(property, annotations, IsHandledByConvention);
}

Expand Down Expand Up @@ -155,6 +164,11 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
RelationalAnnotationNames.ViewColumnName, nameof(RelationalPropertyBuilderExtensions.HasViewColumnName),
methodCallCodeFragments);

GenerateSimpleFluentApiCall(
annotations,
RelationalAnnotationNames.FunctionColumnName, nameof(RelationalPropertyBuilderExtensions.HasFunctionColumnName),
methodCallCodeFragments);

GenerateSimpleFluentApiCall(
annotations,
RelationalAnnotationNames.DefaultValueSql, nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// 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 System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
Expand Down Expand Up @@ -37,16 +41,7 @@ public static EntityTypeBuilder ToTable(
[NotNull] this EntityTypeBuilder entityTypeBuilder,
[CanBeNull] string name,
bool excludedFromMigrations)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NullButNotEmpty(name, nameof(name));

entityTypeBuilder.Metadata.SetTableName(name);
entityTypeBuilder.Metadata.SetSchema(null);
entityTypeBuilder.Metadata.SetIsTableExcludedFromMigrations(excludedFromMigrations);

return entityTypeBuilder;
}
=> entityTypeBuilder.ToTable(name, null, excludedFromMigrations);

/// <summary>
/// Configures the table that the entity type maps to when targeting a relational database.
Expand Down Expand Up @@ -168,16 +163,7 @@ public static OwnedNavigationBuilder ToTable(
[NotNull] this OwnedNavigationBuilder referenceOwnershipBuilder,
[CanBeNull] string name,
bool excludedFromMigrations)
{
Check.NotNull(referenceOwnershipBuilder, nameof(referenceOwnershipBuilder));
Check.NullButNotEmpty(name, nameof(name));

referenceOwnershipBuilder.OwnedEntityType.SetTableName(name);
referenceOwnershipBuilder.OwnedEntityType.SetSchema(null);
referenceOwnershipBuilder.OwnedEntityType.SetIsTableExcludedFromMigrations(excludedFromMigrations);

return referenceOwnershipBuilder;
}
=> referenceOwnershipBuilder.ToTable(name, null, excludedFromMigrations);

/// <summary>
/// Configures the table that the entity type maps to when targeting a relational database.
Expand Down Expand Up @@ -442,16 +428,7 @@ public static bool CanExcludeTableFromMigrations(
public static EntityTypeBuilder ToView(
[NotNull] this EntityTypeBuilder entityTypeBuilder,
[CanBeNull] string name)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NullButNotEmpty(name, nameof(name));

entityTypeBuilder.Metadata.SetViewName(name);
entityTypeBuilder.Metadata.SetViewSchema(null);
entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null);

return entityTypeBuilder;
}
=> entityTypeBuilder.ToView(name, null);

/// <summary>
/// Configures the view that the entity type maps to when targeting a relational database.
Expand Down Expand Up @@ -504,6 +481,198 @@ public static EntityTypeBuilder<TEntity> ToView<TEntity>(
where TEntity : class
=> (EntityTypeBuilder<TEntity>)ToView((EntityTypeBuilder)entityTypeBuilder, name, schema);

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <returns> The function configuration builder. </returns>
public static EntityTypeBuilder ToFunction(
[NotNull] this EntityTypeBuilder entityTypeBuilder,
[CanBeNull] string name)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NullButNotEmpty(name, nameof(name));

CreateFunction(name, entityTypeBuilder.Metadata);

return entityTypeBuilder;
}

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <param name="configureFunction"> The function configuration action. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static EntityTypeBuilder ToFunction(
[NotNull] this EntityTypeBuilder entityTypeBuilder,
[CanBeNull] string name,
[NotNull] Action<TableValuedFunctionBuilder> configureFunction)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NullButNotEmpty(name, nameof(name));
Check.NotNull(configureFunction, nameof(configureFunction));

configureFunction(new TableValuedFunctionBuilder(CreateFunction(name, entityTypeBuilder.Metadata)));

return entityTypeBuilder;
}

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <returns> The function configuration builder. </returns>
public static EntityTypeBuilder<TEntity> ToFunction<TEntity>(
[NotNull] this EntityTypeBuilder<TEntity> entityTypeBuilder,
[CanBeNull] string name)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)ToFunction((EntityTypeBuilder)entityTypeBuilder, name);

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <param name="configureFunction"> The function configuration action. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static EntityTypeBuilder<TEntity> ToFunction<TEntity>(
[NotNull] this EntityTypeBuilder<TEntity> entityTypeBuilder,
[CanBeNull] string name,
[NotNull] Action<TableValuedFunctionBuilder> configureFunction)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)ToFunction((EntityTypeBuilder)entityTypeBuilder, name, configureFunction);

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <param name="ownedNavigationBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <returns> The function configuration builder. </returns>
public static OwnedNavigationBuilder ToFunction(
[NotNull] this OwnedNavigationBuilder ownedNavigationBuilder,
[CanBeNull] string name)
{
Check.NotNull(ownedNavigationBuilder, nameof(ownedNavigationBuilder));
Check.NullButNotEmpty(name, nameof(name));

CreateFunction(name, ownedNavigationBuilder.OwnedEntityType);

return ownedNavigationBuilder;
}

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <param name="ownedNavigationBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <param name="configureFunction"> The function configuration action. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static OwnedNavigationBuilder ToFunction(
[NotNull] this OwnedNavigationBuilder ownedNavigationBuilder,
[CanBeNull] string name,
[NotNull] Action<TableValuedFunctionBuilder> configureFunction)
{
Check.NotNull(ownedNavigationBuilder, nameof(ownedNavigationBuilder));
Check.NullButNotEmpty(name, nameof(name));
Check.NotNull(configureFunction, nameof(configureFunction));

configureFunction(new TableValuedFunctionBuilder(CreateFunction(name, ownedNavigationBuilder.OwnedEntityType)));

return ownedNavigationBuilder;
}

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <typeparam name="TRelatedEntity"> The entity type that this relationship targets. </typeparam>
/// <param name="referenceOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <returns> The function configuration builder. </returns>
public static OwnedNavigationBuilder<TEntity, TRelatedEntity> ToFunction<TEntity, TRelatedEntity>(
[NotNull] this OwnedNavigationBuilder<TEntity, TRelatedEntity> referenceOwnershipBuilder,
[CanBeNull] string name)
where TEntity : class
where TRelatedEntity : class
=> (OwnedNavigationBuilder<TEntity, TRelatedEntity>)ToFunction((OwnedNavigationBuilder)referenceOwnershipBuilder, name);

/// <summary>
/// Configures the function that the entity type maps to when targeting a relational database.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <typeparam name="TRelatedEntity"> The entity type that this relationship targets. </typeparam>
/// <param name="referenceOwnershipBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the function. </param>
/// <param name="configureFunction"> The function configuration action. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static OwnedNavigationBuilder<TEntity, TRelatedEntity> ToFunction<TEntity, TRelatedEntity>(
[NotNull] this OwnedNavigationBuilder<TEntity, TRelatedEntity> referenceOwnershipBuilder,
[CanBeNull] string name,
[NotNull] Action<TableValuedFunctionBuilder> configureFunction)
where TEntity : class
where TRelatedEntity : class
=> (OwnedNavigationBuilder<TEntity, TRelatedEntity>)ToFunction(
(OwnedNavigationBuilder)referenceOwnershipBuilder, name, configureFunction);

private static IMutableDbFunction CreateFunction(string name, IMutableEntityType entityType)
{
entityType.SetFunctionName(name);

var model = entityType.Model;
var function = model.FindDbFunction(name)
?? model.AddDbFunction(name, typeof(IQueryable<>).MakeGenericType(entityType.ClrType ?? typeof(Dictionary<string, object>)));

return function;
}

/// <summary>
/// Configures the table that the entity type maps to when targeting a relational database.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the table. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
public static IConventionEntityTypeBuilder ToFunction(
[NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false)
{
if (!entityTypeBuilder.CanSetFunction(name, fromDataAnnotation))
{
return null;
}

var entityType = entityTypeBuilder.Metadata;
entityType.SetFunctionName(name, fromDataAnnotation);

entityType.Model.Builder.HasDbFunction(name, typeof(IQueryable<>).MakeGenericType(entityType.ClrType), fromDataAnnotation);

return entityTypeBuilder;
}

/// <summary>
/// Returns a value indicating whether the view or table name can be set for this entity type
/// using the specified configuration source.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the view or table. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the configuration can be applied. </returns>
public static bool CanSetFunction(
[NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false)
{
Check.NullButNotEmpty(name, nameof(name));

return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.FunctionName, name, fromDataAnnotation);
}

/// <summary>
/// Configures a database check constraint when targeting a relational database.
/// </summary>
Expand Down
Loading

0 comments on commit 297130c

Please sign in to comment.