Skip to content

Commit

Permalink
Query: Allow using STET overload for Set in query filter/defining query
Browse files Browse the repository at this point in the history
- Update with correct query root when replacing with runtime entity type in query filter

Resolves #24601
  • Loading branch information
smitpatel committed Aug 19, 2021
1 parent f62c86b commit 2a0f233
Show file tree
Hide file tree
Showing 19 changed files with 408 additions and 14 deletions.
13 changes: 13 additions & 0 deletions src/EFCore.Cosmos/Query/Internal/FromSqlQueryRootExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -81,6 +82,18 @@ public FromSqlQueryRootExpression(
public override Expression DetachQueryProvider()
=> new FromSqlQueryRootExpression(EntityType, Sql, Argument);

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new FromSqlQueryRootExpression(entityType, Sql, Argument);

/// <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
Expand Down
13 changes: 13 additions & 0 deletions src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;

Expand Down Expand Up @@ -80,6 +81,18 @@ public FromSqlQueryRootExpression(
public override Expression DetachQueryProvider()
=> new FromSqlQueryRootExpression(EntityType, Sql, Argument);

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new FromSqlQueryRootExpression(entityType, Sql, Argument);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;

Expand Down Expand Up @@ -76,6 +77,18 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
: this;
}

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new TableValuedFunctionQueryRootExpression(entityType, Function, Arguments);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;

Expand Down Expand Up @@ -46,6 +47,18 @@ public TemporalAllQueryRootExpression(IAsyncQueryProvider queryProvider, IEntity
public override Expression DetachQueryProvider()
=> new TemporalAllQueryRootExpression(EntityType);

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new TemporalAllQueryRootExpression(entityType);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;

Expand Down Expand Up @@ -58,6 +59,18 @@ public TemporalAsOfQueryRootExpression(
public override Expression DetachQueryProvider()
=> new TemporalAsOfQueryRootExpression(EntityType, PointInTime);

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new TemporalAsOfQueryRootExpression(entityType, PointInTime);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;

Expand Down Expand Up @@ -53,6 +54,18 @@ public TemporalBetweenQueryRootExpression(
public override Expression DetachQueryProvider()
=> new TemporalBetweenQueryRootExpression(EntityType, From, To);

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new TemporalBetweenQueryRootExpression(entityType, From, To);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;

Expand Down Expand Up @@ -53,6 +54,18 @@ public TemporalContainedInQueryRootExpression(
public override Expression DetachQueryProvider()
=> new TemporalContainedInQueryRootExpression(EntityType, From, To);

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new TemporalContainedInQueryRootExpression(entityType, From, To);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;

Expand Down Expand Up @@ -53,6 +54,18 @@ public TemporalFromToQueryRootExpression(
public override Expression DetachQueryProvider()
=> new TemporalFromToQueryRootExpression(EntityType, From, To);

/// <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 QueryRootExpression UpdateEntityType(IEntityType entityType)
=> entityType.ClrType != EntityType.ClrType
|| entityType.Name != EntityType.Name
? throw new InvalidOperationException(CoreStrings.QueryRootDifferentEntityType(entityType.DisplayName()))
: new TemporalFromToQueryRootExpression(entityType, From, To);

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Utilities;

Expand Down Expand Up @@ -111,7 +114,55 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
&& methodCallExpression.Type.GetGenericTypeDefinition() == typeof(DbSet<>)
&& _model != null)
{
return new QueryRootExpression(FindEntityType(methodCallExpression.Type)!);
IEntityType? entityType;
var returnType = methodCallExpression.Type.GetGenericArguments()[0];
if (methodCallExpression.Arguments.Count == 1)
{
// STET Set method
var entityTypeName = methodCallExpression.Arguments[0].GetConstantValue<string>();
entityType = (IEntityType?)_model.FindEntityType(entityTypeName);
}
else
{
entityType = FindEntityType(returnType);
}

if (entityType == null)
{
if (_model.IsShared(returnType))
{
throw new InvalidOperationException(CoreStrings.InvalidSetSharedType(returnType.ShortDisplayName()));
}

var findSameTypeName = ((IModel)_model).FindSameTypeNameWithDifferentNamespace(returnType);
//if the same name exists in your entity types we will show you the full namespace of the type
if (!string.IsNullOrEmpty(findSameTypeName))
{
throw new InvalidOperationException(CoreStrings.InvalidSetSameTypeWithDifferentNamespace(returnType.DisplayName(), findSameTypeName));
}
else
{
throw new InvalidOperationException(CoreStrings.InvalidSetType(returnType.ShortDisplayName()));
}
}

if (entityType.IsOwned())
{
var message = CoreStrings.InvalidSetTypeOwned(
entityType.DisplayName(), entityType.FindOwnership()!.PrincipalEntityType.DisplayName());

throw new InvalidOperationException(message);
}

if (entityType.ClrType != returnType)
{
var message = CoreStrings.DbSetIncorrectGenericType(
entityType.ShortName(), entityType.ClrType.ShortDisplayName(), returnType.ShortDisplayName());

throw new InvalidOperationException(message);
}

return new QueryRootExpression(entityType);
}

return base.VisitMethodCall(methodCallExpression);
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ public Expression Rewrite(Expression expression)
/// <inheritdoc />
protected override Expression VisitExtension(Expression extensionExpression)
=> extensionExpression is QueryRootExpression queryRootExpression
? new QueryRootExpression(_model.FindEntityType(queryRootExpression.EntityType.Name)!)
? queryRootExpression.UpdateEntityType(_model.FindEntityType(queryRootExpression.EntityType.Name)!)
: base.VisitExtension(extensionExpression);
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@
</data>
<data name="LogShadowForeignKeyPropertyCreated" xml:space="preserve">
<value>The foreign key property '{entityType}.{property}' was created in shadow state because a conflicting property with the simple name '{baseName}' exists in the entity type, but is either not mapped, is already used for another relationship, or is incompatible with the associated primary key type. See https://aka.ms/efcore-relationships for information on mapping relationships in EF Core.</value>
<comment>Warning CoreEventId.ShadowForeignKeyPropertyCreated string string</comment>
<comment>Warning CoreEventId.ShadowForeignKeyPropertyCreated string string string</comment>
</data>
<data name="LogShadowPropertyCreated" xml:space="preserve">
<value>The property '{entityType}.{property}' was created in shadow state because there are no eligible CLR members with a matching name.</value>
Expand Down Expand Up @@ -1310,6 +1310,9 @@
<data name="QueryInvalidMaterializationType" xml:space="preserve">
<value>The query contains a projection '{projection}' of type '{queryableType}'. Collections in the final projection must be an 'IEnumerable&lt;T&gt;' type such as 'List&lt;T&gt;'. Consider using 'ToList' or some other mechanism to convert the 'IQueryable&lt;T&gt;' or 'IOrderedEnumerable&lt;T&gt;' into an 'IEnumerable&lt;T&gt;'.</value>
</data>
<data name="QueryRootDifferentEntityType" xml:space="preserve">
<value>The replacement entity type: {entityType} does not have same name and CLR type as entity type this query root represents.</value>
</data>
<data name="QueryUnableToTranslateEFProperty" xml:space="preserve">
<value>Translation of '{expression}' failed. Either the query source is not an entity type, or the specified property does not exist on the entity type.</value>
</data>
Expand Down
Loading

0 comments on commit 2a0f233

Please sign in to comment.