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

Entity equality support for non-extension Contains #16841

Merged
merged 1 commit into from
Aug 4, 2019
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
2 changes: 0 additions & 2 deletions src/EFCore/Extensions/Internal/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

Expand Down
12 changes: 10 additions & 2 deletions 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 @@ -1180,12 +1180,15 @@
<data name="PropertyClashingNonIndexer" xml:space="preserve">
<value>The indexed property '{property}' cannot be added to type '{entityType}' because the CLR class contains a member with the same name.</value>
</data>
<data name="SubqueryWithCompositeKeyNotSupported" xml:space="preserve">
<data name="EntityEqualitySubqueryWithCompositeKeyNotSupported" xml:space="preserve">
<value>This query would cause multiple evaluation of a subquery because entity '{entityType}' has a composite key. Rewrite your query avoiding the subquery.</value>
</data>
<data name="EntityEqualityContainsWithCompositeKeyNotSupported" xml:space="preserve">
<value>Cannot translate a Contains() operator on entity '{entityType}' because it has a composite key.</value>
</data>
<data name="EntityEqualityOnKeylessEntityNotSupported" xml:space="preserve">
<value>Comparison on entity type '{entityType}' is not supported because it is a keyless entity.</value>
</data>
<data name="UnableToDiscriminate" xml:space="preserve">
<value>Unable to materialize entity of type '{entityType}'. No discriminators matched '{discriminator}'.</value>
</data>
Expand Down
193 changes: 145 additions & 48 deletions src/EFCore/Query/Internal/EntityEqualityRewritingExpressionVisitor.cs

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions src/EFCore/Query/QueryCompilationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,11 @@ public virtual Func<QueryContext, TResult> CreateQueryExecutor<TResult>(Expressi
/// A lambda must be provided, which will extract the parameter's value from the QueryContext every time
/// the query is executed.
/// </summary>
public virtual void RegisterRuntimeParameter(string name, LambdaExpression valueExtractor)
public virtual ParameterExpression RegisterRuntimeParameter(string name, LambdaExpression valueExtractor, Type type)
{
if (valueExtractor.Parameters.Count != 1
|| valueExtractor.Parameters[0] != QueryContextParameter
|| valueExtractor.ReturnType != typeof(object))
if (valueExtractor.Parameters.Count != 1 || valueExtractor.Parameters[0] != QueryContextParameter)
{
throw new ArgumentException("Runtime parameter extraction lambda must have one QueryContext parameter and return an object",
throw new ArgumentException("Runtime parameter extraction lambda must have one QueryContext parameter",
nameof(valueExtractor));
}

Expand All @@ -110,6 +108,7 @@ public virtual void RegisterRuntimeParameter(string name, LambdaExpression value
}

_runtimeParameters[name] = valueExtractor;
return Expression.Parameter(type, name);
}

private Expression InsertRuntimeParameters(Expression query)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,39 @@ FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] = 10248))");
}

[ConditionalTheory(Skip = "Issue#14935 (Contains not implemented)")]
public override async Task List_Contains_over_entityType_should_rewrite_to_identity_equality(bool isAsync)
{
await base.List_Contains_over_entityType_should_rewrite_to_identity_equality(isAsync);

AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] = 10248))");
}

[ConditionalTheory(Skip = "Issue#14935 (Contains not implemented)")]
public override async Task List_Contains_with_constant_list(bool isAsync)
{
await base.List_Contains_with_constant_list(isAsync);

AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] = 10248))");
}

[ConditionalTheory(Skip = "Issue#14935 (Contains not implemented)")]
public override async Task List_Contains_with_parameter_list(bool isAsync)
{
await base.List_Contains_with_parameter_list(isAsync);

AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] = 10248))");
}

[ConditionalTheory(Skip = "Issue#14935 (Contains not implemented)")]
public override void Contains_over_entityType_with_null_should_rewrite_to_identity_equality()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
Expand Down Expand Up @@ -1510,6 +1511,53 @@ var query
}
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task List_Contains_over_entityType_should_rewrite_to_identity_equality(bool isAsync)
{
var someOrder = new Order { OrderID = 10248 };

return AssertQuery<Customer>(isAsync, cs =>
cs.Where(c => c.Orders.Contains(someOrder)),
entryCount: 1);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task List_Contains_with_constant_list(bool isAsync)
{
return AssertQuery<Customer>(isAsync, cs =>
cs.Where(c => new List<Customer>
{
new Customer { CustomerID = "ALFKI" },
new Customer { CustomerID = "ANATR" }
}.Contains(c)),
entryCount: 2);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task List_Contains_with_parameter_list(bool isAsync)
{
var customers = new List<Customer>
{
new Customer { CustomerID = "ALFKI" },
new Customer { CustomerID = "ANATR" }
};

return AssertQuery<Customer>(isAsync, cs => cs.Where(c => customers.Contains(c)),
entryCount: 2);
}

[ConditionalFact]
public virtual void Contains_over_keyless_entity_throws()
{
using (var context = CreateContext())
{
Assert.Throws<InvalidOperationException>(() => context.CustomerQueries.Contains(new CustomerView()));
}
}

[ConditionalFact]
public virtual void Contains_over_entityType_with_null_should_rewrite_to_identity_equality()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ where c.Orders.FirstOrDefault() != null
[ConditionalFact]
public virtual void Entity_equality_through_subquery_composite_key()
{
Assert.Throws<NotSupportedException>(() =>
Assert.Throws<InvalidOperationException>(() =>
CreateContext().Orders
.Where(o => o.OrderDetails.FirstOrDefault() == new OrderDetail
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public override bool Equals(object obj)

public override int GetHashCode()
// ReSharper disable once NonReadonlyMemberInGetHashCode
=> CompanyName.GetHashCode();
=> CompanyName?.GetHashCode() ?? 0;

public override string ToString()
=> "CustomerView " + CompanyName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,42 @@ ELSE CAST(0 AS bit)
END");
}

public override async Task List_Contains_over_entityType_should_rewrite_to_identity_equality(bool isAsync)
{
await base.List_Contains_over_entityType_should_rewrite_to_identity_equality(isAsync);

AssertSql(
@"@__entity_equality_someOrder_0_OrderID='10248'

SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
WHERE @__entity_equality_someOrder_0_OrderID IN (
SELECT [o].[OrderID]
FROM [Orders] AS [o]
WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL
)");
}

public override async Task List_Contains_with_constant_list(bool isAsync)
{
await base.List_Contains_with_constant_list(isAsync);

AssertSql(
@"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR')");
}

public override async Task List_Contains_with_parameter_list(bool isAsync)
{
await base.List_Contains_with_parameter_list(isAsync);

AssertSql(
@"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR')");
}

public override void Contains_over_entityType_with_null_should_rewrite_to_identity_equality()
{
base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality();
Expand Down