Skip to content

Commit

Permalink
Deprecate defining query at Core level
Browse files Browse the repository at this point in the history
Fixes #18903
  • Loading branch information
AndriySvyryd authored Jul 15, 2020
1 parent 94452ca commit 66bb342
Show file tree
Hide file tree
Showing 39 changed files with 452 additions and 161 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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.Linq;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Extension methods for <see cref="EntityTypeBuilder" /> for the in-memory provider.
/// </summary>
public static class InMemoryEntityTypeBuilderExtensions
{
/// <summary>
/// Configures a query used to provide data for an entity type.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="query"> The query that will provide the underlying data for the entity type. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static EntityTypeBuilder<TEntity> ToQuery<TEntity>(
[NotNull] this EntityTypeBuilder<TEntity> entityTypeBuilder,
[NotNull] Expression<Func<IQueryable<TEntity>>> query)
where TEntity : class
{
Check.NotNull(query, nameof(query));

InMemoryEntityTypeExtensions.SetDefiningQuery(entityTypeBuilder.Metadata, query);

return entityTypeBuilder;
}

/// <summary>
/// Configures a query used to provide data for an entity type.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="query"> The query that will provide the underlying data for the entity type. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns>
/// The same builder instance if the query was set, <see langword="null" /> otherwise.
/// </returns>
public static IConventionEntityTypeBuilder HasDefiningQuery(
[NotNull] this IConventionEntityTypeBuilder entityTypeBuilder,
[CanBeNull] LambdaExpression query,
bool fromDataAnnotation = false)
{
if (CanSetDefiningQuery(entityTypeBuilder, query, fromDataAnnotation))
{
InMemoryEntityTypeExtensions.SetDefiningQuery(entityTypeBuilder.Metadata, query, fromDataAnnotation);

return entityTypeBuilder;
}

return null;
}

/// <summary>
/// Returns a value indicating whether the given defining query can be set from the current configuration source.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="query"> The query that will provide the underlying data for the keyless entity type. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the given defining query can be set. </returns>
public static bool CanSetDefiningQuery(
[NotNull] this IConventionEntityTypeBuilder entityTypeBuilder,
[CanBeNull] LambdaExpression query,
bool fromDataAnnotation = false)
#pragma warning disable EF1001 // Internal EF Core API usage.
=> entityTypeBuilder.CanSetAnnotation(CoreAnnotationNames.DefiningQuery, query, fromDataAnnotation);
#pragma warning restore EF1001 // Internal EF Core API usage.
}
}
68 changes: 68 additions & 0 deletions src/EFCore.InMemory/Extensions/InMemoryEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Extension methods for <see cref="IEntityType" /> for the in-memory provider.
/// </summary>
public static class InMemoryEntityTypeExtensions
{
/// <summary>
/// Gets the LINQ query used as the default source for queries of this type.
/// </summary>
/// <param name="entityType"> The entity type to get the defining query for. </param>
/// <returns> The LINQ query used as the default source. </returns>
public static LambdaExpression GetDefiningQuery([NotNull] this IEntityType entityType)
#pragma warning disable EF1001 // Internal EF Core API usage.
=> (LambdaExpression)Check.NotNull(entityType, nameof(entityType))[CoreAnnotationNames.DefiningQuery];
#pragma warning restore EF1001 // Internal EF Core API usage.

/// <summary>
/// Sets the LINQ query used as the default source for queries of this type.
/// </summary>
/// <param name="entityType"> The entity type. </param>
/// <param name="definingQuery"> The LINQ query used as the default source. </param>
public static void SetDefiningQuery(
[NotNull] this IMutableEntityType entityType,
[CanBeNull] LambdaExpression definingQuery)
=> Check.NotNull(entityType, nameof(entityType))
#pragma warning disable EF1001 // Internal EF Core API usage.
.SetOrRemoveAnnotation(CoreAnnotationNames.DefiningQuery, definingQuery);
#pragma warning restore EF1001 // Internal EF Core API usage.

/// <summary>
/// Sets the LINQ query used as the default source for queries of this type.
/// </summary>
/// <param name="entityType"> The entity type. </param>
/// <param name="definingQuery"> The LINQ query used as the default source. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> The configured entity type. </returns>
public static LambdaExpression SetDefiningQuery(
[NotNull] this IConventionEntityType entityType,
[CanBeNull] LambdaExpression definingQuery,
bool fromDataAnnotation = false)
=> (LambdaExpression)Check.NotNull(entityType, nameof(entityType))
#pragma warning disable EF1001 // Internal EF Core API usage.
.SetOrRemoveAnnotation(CoreAnnotationNames.DefiningQuery, definingQuery, fromDataAnnotation)
#pragma warning restore EF1001 // Internal EF Core API usage.
?.Value;

/// <summary>
/// Returns the configuration source for <see cref="EntityTypeExtensions.GetDefiningQuery" />.
/// </summary>
/// <param name="entityType"> The entity type. </param>
/// <returns> The configuration source for <see cref="EntityTypeExtensions.GetDefiningQuery" />. </returns>
public static ConfigurationSource? GetDefiningQueryConfigurationSource([NotNull] this IConventionEntityType entityType)
#pragma warning disable EF1001 // Internal EF Core API usage.
=> entityType.FindAnnotation(CoreAnnotationNames.DefiningQuery)?.GetConfigurationSource();
#pragma warning restore EF1001 // Internal EF Core API usage.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.InMemory.Internal;
using Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.InMemory.Query.Internal;
using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
Expand Down Expand Up @@ -55,9 +56,8 @@ public static IServiceCollection AddEntityFrameworkInMemoryDatabase([NotNull] th
.TryAdd<IDatabaseCreator, InMemoryDatabaseCreator>()
.TryAdd<IQueryContextFactory, InMemoryQueryContextFactory>()
.TryAdd<IProviderConventionSetBuilder, InMemoryConventionSetBuilder>()
.TryAdd<IModelValidator, InMemoryModelValidator>()
.TryAdd<ITypeMappingSource, InMemoryTypeMappingSource>()

// New Query pipeline
.TryAdd<IShapedQueryCompilingExpressionVisitorFactory, InMemoryShapedQueryCompilingExpressionVisitorFactory>()
.TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory, InMemoryQueryableMethodTranslatingExpressionVisitorFactory>()
.TryAdd<ISingletonOptions, IInMemorySingletonOptions>(p => p.GetService<IInMemorySingletonOptions>())
Expand Down
68 changes: 68 additions & 0 deletions src/EFCore.InMemory/Internal/InMemoryModelValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.InMemory.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class InMemoryModelValidator : ModelValidator
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public InMemoryModelValidator([NotNull] ModelValidatorDependencies dependencies)
: base(dependencies)
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
base.Validate(model, logger);

ValidateDefiningQuery(model, logger);
}

/// <summary>
/// Validates the configuration of defining queries in the model.
/// </summary>
/// <param name="model"> The model to validate. </param>
/// <param name="logger"> The logger to use. </param>
protected virtual void ValidateDefiningQuery(
[NotNull] IModel model, [NotNull] IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
Check.NotNull(model, nameof(model));

foreach (var entityType in model.GetEntityTypes())
{
if (InMemoryEntityTypeExtensions.GetDefiningQuery(entityType) != null)
{
if (entityType.BaseType != null)
{
throw new InvalidOperationException(
CoreStrings.DerivedTypeDefiningQuery(entityType.DisplayName(), entityType.BaseType.DisplayName()));
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// Convention that converts accesses of <see cref="DbSet{TEntity}"/> inside query filters and defining queries into <see cref="QueryRootExpression"/>.
/// This makes them consistent with how DbSet accesses in the actual queries are represented, which allows for easier processing in the
/// query pipeline.
/// </summary>
public class DefiningQueryRewritingConvention : QueryFilterRewritingConvention
{
/// <summary>
/// Creates a new instance of <see cref="QueryFilterRewritingConvention" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
public DefiningQueryRewritingConvention([NotNull] ProviderConventionSetBuilderDependencies dependencies)
: base(dependencies)
{
}

/// <inheritdoc />
public override void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var definingQuery = InMemoryEntityTypeExtensions.GetDefiningQuery(entityType);
if (definingQuery != null)
{
InMemoryEntityTypeExtensions.SetDefiningQuery(
entityType, (LambdaExpression)DbSetAccessRewriter.Rewrite(modelBuilder.Metadata, definingQuery));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ public InMemoryConventionSetBuilder(
{
}

/// <inheritdoc />
public override ConventionSet CreateConventionSet()
{
var conventionSet = base.CreateConventionSet();

conventionSet.ModelFinalizingConventions.Add(new DefiningQueryRewritingConvention(Dependencies));

return conventionSet;
}

/// <summary>
/// <para>
/// Call this method to build a <see cref="ConventionSet" /> for the in-memory provider when using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ public static string GetTableName([NotNull] this IEntityType entityType)

return (entityType as IConventionEntityType)?.GetViewNameConfigurationSource() == null
&& ((entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null)
#pragma warning disable CS0618 // Type or member is obsolete
&& ((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null)
#pragma warning restore CS0618 // Type or member is obsolete
? GetDefaultTableName(entityType)
: null;
}
Expand Down Expand Up @@ -248,7 +250,9 @@ public static string GetViewName([NotNull] this IEntityType entityType)
}

return ((entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null)
#pragma warning disable CS0618 // Type or member is obsolete
&& (entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null
#pragma warning restore CS0618 // Type or member is obsolete
? GetDefaultViewName(entityType)
: null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public override ConventionSet CreateConventionSet()
conventionSet.ModelFinalizingConventions.Add(new DbFunctionTypeMappingConvention(Dependencies, RelationalDependencies));
ReplaceConvention(
conventionSet.ModelFinalizingConventions,
(QueryFilterDefiningQueryRewritingConvention)new RelationalQueryFilterDefiningQueryRewritingConvention(
(QueryFilterRewritingConvention)new RelationalQueryFilterRewritingConvention(
Dependencies, RelationalDependencies));

ConventionSet.AddAfter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
Expand All @@ -12,21 +13,44 @@
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <inheritdoc />
public class RelationalQueryFilterDefiningQueryRewritingConvention : QueryFilterDefiningQueryRewritingConvention
public class RelationalQueryFilterRewritingConvention : QueryFilterRewritingConvention
{
/// <summary>
/// Creates a new instance of <see cref="RelationalQueryFilterDefiningQueryRewritingConvention" />.
/// Creates a new instance of <see cref="RelationalQueryFilterRewritingConvention" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
/// <param name="relationalDependencies"> Parameter object containing relational dependencies for this convention. </param>
public RelationalQueryFilterDefiningQueryRewritingConvention(
public RelationalQueryFilterRewritingConvention(
[NotNull] ProviderConventionSetBuilderDependencies dependencies,
[NotNull] RelationalConventionSetBuilderDependencies relationalDependencies)
: base(dependencies)
{
DbSetAccessRewriter = new RelationalDbSetAccessRewritingExpressionVisitor(Dependencies.ContextType);
}

/// <inheritdoc />
public override void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var queryFilter = entityType.GetQueryFilter();
if (queryFilter != null)
{
entityType.SetQueryFilter((LambdaExpression)DbSetAccessRewriter.Rewrite(modelBuilder.Metadata, queryFilter));
}

#pragma warning disable CS0618 // Type or member is obsolete
var definingQuery = entityType.GetDefiningQuery();
if (definingQuery != null)
{
entityType.SetDefiningQuery((LambdaExpression)DbSetAccessRewriter.Rewrite(modelBuilder.Metadata, definingQuery));
}
#pragma warning restore CS0618 // Type or member is obsolete
}
}

/// <inheritdoc />
protected class RelationalDbSetAccessRewritingExpressionVisitor : DbSetAccessRewritingExpressionVisitor
{
Expand Down
Loading

0 comments on commit 66bb342

Please sign in to comment.