Skip to content

Commit

Permalink
Make DbFunctions non-evaluatable (#22318)
Browse files Browse the repository at this point in the history
Fixes #22317
  • Loading branch information
roji committed Aug 31, 2020
1 parent d38d7df commit e1fc483
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,22 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
Check.NotNull(expression, nameof(expression));
Check.NotNull(model, nameof(model));

if (expression is MethodCallExpression methodCallExpression
&& model.FindDbFunction(methodCallExpression.Method) != null)
if (expression is MethodCallExpression methodCallExpression)
{
// Never evaluate DbFunction
// If it is inside lambda then we will have whole method call
// If it is outside of lambda then it will be evaluated for table valued function already.
return false;
var method = methodCallExpression.Method;

if (model.FindDbFunction(method) != null)
{
// Never evaluate DbFunction
// If it is inside lambda then we will have whole method call
// If it is outside of lambda then it will be evaluated for table valued function already.
return false;
}

if (method.DeclaringType == typeof(RelationalDbFunctionsExtensions))
{
return false;
}
}

return base.IsEvaluatableExpression(expression, model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static IServiceCollection AddEntityFrameworkSqlServer([NotNull] this ISer
.TryAdd<IModelValidator, SqlServerModelValidator>()
.TryAdd<IProviderConventionSetBuilder, SqlServerConventionSetBuilder>()
.TryAdd<IUpdateSqlGenerator>(p => p.GetService<ISqlServerUpdateSqlGenerator>())
.TryAdd<IEvaluatableExpressionFilter, SqlServerEvaluatableExpressionFilter>()
.TryAdd<IRelationalTransactionFactory, SqlServerTransactionFactory>()
.TryAdd<IModificationCommandBatchFactory, SqlServerModificationCommandBatchFactory>()
.TryAdd<IValueGeneratorSelector, SqlServerValueGeneratorSelector>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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.Query;

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.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 SqlServerEvaluatableExpressionFilter : RelationalEvaluatableExpressionFilter
{
/// <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 SqlServerEvaluatableExpressionFilter([NotNull] EvaluatableExpressionFilterDependencies dependencies, [NotNull] RelationalEvaluatableExpressionFilterDependencies relationalDependencies)
: base(dependencies, relationalDependencies)
{
}

/// <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 bool IsEvaluatableExpression(Expression expression, IModel model)
{
if (expression is MethodCallExpression methodCallExpression
&& methodCallExpression.Method.DeclaringType == typeof(SqlServerDbFunctionsExtensions))
{
return false;
}

return base.IsEvaluatableExpression(expression, model);
}
}
}
4 changes: 3 additions & 1 deletion src/EFCore/Query/EvaluatableExpressionFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.Expressions;
using System.Reflection;
using JetBrains.Annotations;
Expand Down Expand Up @@ -108,7 +109,8 @@ public virtual bool IsEvaluatableExpression(Expression expression, IModel model)
if (Equals(method, _guidNewGuid)
|| Equals(method, _randomNextNoArgs)
|| Equals(method, _randomNextOneArg)
|| Equals(method, _randomNextTwoArgs))
|| Equals(method, _randomNextTwoArgs)
|| method.DeclaringType == typeof(DbFunctionsExtensions))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ public virtual Task Collate_case_sensitive(bool async)
c => EF.Functions.Collate(c.ContactName, CaseSensitiveCollation) == "maria anders",
c => c.ContactName.Equals("maria anders", StringComparison.Ordinal));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Collate_case_sensitive_constant(bool async)
=> AssertCount(
async,
ss => ss.Set<Customer>(),
ss => ss.Set<Customer>(),
c => c.ContactName == EF.Functions.Collate("maria anders", CaseSensitiveCollation),
c => c.ContactName.Equals("maria anders", StringComparison.Ordinal));

protected abstract string CaseInsensitiveCollation { get; }
protected abstract string CaseSensitiveCollation { get; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ public virtual Task Like_literal_with_escape(bool async)
c => EF.Functions.Like(c.ContactName, "!%", "!"),
c => c.ContactName.Contains("%"));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Like_all_literals(bool async)
=> AssertCount(
async,
ss => ss.Set<Customer>(),
ss => ss.Set<Customer>(),
c => EF.Functions.Like("FOO", "%O%"),
c => "FOO".Contains("O") || "FOO".Contains("m"));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Like_all_literals_with_escape(bool async)
=> AssertCount(
async,
ss => ss.Set<Customer>(),
ss => ss.Set<Customer>(),
c => EF.Functions.Like("%", "!%", "!"),
c => "%".Contains("%"));

protected NorthwindContext CreateContext()
=> Fixture.CreateContext();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ FROM [Customers] AS [c]
WHERE [c].[ContactName] LIKE N'!%' ESCAPE N'!'");
}

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

AssertSql(
@"SELECT COUNT(*)
FROM [Customers] AS [c]
WHERE N'FOO' LIKE N'%O%'");
}

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

AssertSql(
@"SELECT COUNT(*)
FROM [Customers] AS [c]
WHERE N'%' LIKE N'!%' ESCAPE N'!'");
}

public override async Task Collate_case_insensitive(bool async)
{
await base.Collate_case_insensitive(async);
Expand All @@ -76,6 +96,16 @@ FROM [Customers] AS [c]
WHERE [c].[ContactName] COLLATE Latin1_General_CS_AS = N'maria anders'");
}

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

AssertSql(
@"SELECT COUNT(*)
FROM [Customers] AS [c]
WHERE [c].[ContactName] = N'maria anders' COLLATE Latin1_General_CS_AS");
}

protected override string CaseInsensitiveCollation
=> "Latin1_General_CI_AI";

Expand Down Expand Up @@ -1102,6 +1132,23 @@ FROM [Orders] AS [o]
}
}

[ConditionalFact]
public virtual void DataLength_all_constants()
{
using (var context = CreateContext())
{
var count = context.Orders
.Count(c => EF.Functions.DataLength("foo") == 3);

Assert.Equal(0, count);

AssertSql(
@"SELECT COUNT(*)
FROM [Orders] AS [o]
WHERE CAST(DATALENGTH(N'foo') AS int) = 3");
}
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
Expand Down

0 comments on commit e1fc483

Please sign in to comment.