Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to map entity types to TVFs #21473

Merged
1 commit merged into from
Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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