diff --git a/src/EntityFramework/Core/Common/CommandTrees/DbCommandTree.cs b/src/EntityFramework/Core/Common/CommandTrees/DbCommandTree.cs
index 7e04b44679..c09673ef9e 100644
--- a/src/EntityFramework/Core/Common/CommandTrees/DbCommandTree.cs
+++ b/src/EntityFramework/Core/Common/CommandTrees/DbCommandTree.cs
@@ -17,6 +17,7 @@ public abstract class DbCommandTree
private readonly MetadataWorkspace _metadata;
private readonly DataSpace _dataSpace;
private readonly bool _useDatabaseNullSemantics;
+ private readonly bool _disableFilterOverProjectionSimplificationForCustomFunctions;
internal DbCommandTree()
{
@@ -30,7 +31,7 @@ internal DbCommandTree()
// The logical 'space' that metadata in the expressions used in this command tree must belong to.
// A boolean that indicates whether database null semantics are exhibited when comparing
// two operands, both of which are potentially nullable. The default value is true.
- internal DbCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, bool useDatabaseNullSemantics = true)
+ internal DbCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, bool useDatabaseNullSemantics = true, bool disableFilterOverProjectionSimplificationForCustomFunctions = false)
{
// Ensure the metadata workspace is non-null
DebugCheck.NotNull(metadata);
@@ -44,6 +45,7 @@ internal DbCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, bool use
_metadata = metadata;
_dataSpace = dataSpace;
_useDatabaseNullSemantics = useDatabaseNullSemantics;
+ _disableFilterOverProjectionSimplificationForCustomFunctions = disableFilterOverProjectionSimplificationForCustomFunctions;
}
///
@@ -68,6 +70,11 @@ public bool UseDatabaseNullSemantics
get { return _useDatabaseNullSemantics; }
}
+ public bool DisableFilterOverProjectionSimplificationForCustomFunctions
+ {
+ get { return _disableFilterOverProjectionSimplificationForCustomFunctions; }
+ }
+
///
/// Gets the name and corresponding type of each parameter that can be referenced within this
/// When set to false the validation of the tree is turned off.
/// A boolean that indicates whether database null semantics are exhibited when comparing
/// two operands, both of which are potentially nullable.
+ /// A boolean that indicates whether
+ /// filter over projection simplification should be used.
///
///
/// or
@@ -39,8 +41,8 @@ public sealed class DbQueryCommandTree : DbCommandTree
///
/// does not represent a valid data space
///
- public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExpression query, bool validate, bool useDatabaseNullSemantics)
- : base(metadata, dataSpace, useDatabaseNullSemantics)
+ public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExpression query, bool validate, bool useDatabaseNullSemantics, bool disableFilterOverProjectionSimplificationForCustomFunctions)
+ : base(metadata, dataSpace, useDatabaseNullSemantics, disableFilterOverProjectionSimplificationForCustomFunctions)
{
// Ensure the query expression is non-null
Check.NotNull(query, "query");
@@ -57,6 +59,32 @@ public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExp
_query = query;
}
+ ///
+ /// Constructs a new DbQueryCommandTree that uses the specified metadata workspace.
+ ///
+ /// The metadata workspace that the command tree should use.
+ /// The logical 'space' that metadata in the expressions used in this command tree must belong to.
+ ///
+ /// A that defines the logic of the query.
+ ///
+ /// When set to false the validation of the tree is turned off.
+ /// A boolean that indicates whether database null semantics are exhibited when comparing
+ /// two operands, both of which are potentially nullable.
+ ///
+ ///
+ /// or
+ ///
+ /// is null
+ ///
+ ///
+ ///
+ /// does not represent a valid data space
+ ///
+ public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExpression query, bool validate, bool useDatabaseNullSemantics)
+ : this(metadata, dataSpace, query, validate, useDatabaseNullSemantics, false)
+ {
+ }
+
///
/// Constructs a new DbQueryCommandTree that uses the specified metadata workspace, using database null semantics.
///
@@ -77,7 +105,7 @@ public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExp
/// does not represent a valid data space
///
public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExpression query, bool validate)
- : this(metadata, dataSpace, query, validate, true)
+ : this(metadata, dataSpace, query, validate, true, false)
{
}
@@ -100,7 +128,7 @@ public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExp
/// does not represent a valid data space
///
public DbQueryCommandTree(MetadataWorkspace metadata, DataSpace dataSpace, DbExpression query)
- : this(metadata, dataSpace, query, true, true)
+ : this(metadata, dataSpace, query, true, true, false)
{
}
@@ -147,7 +175,7 @@ internal override string PrintTree(ExpressionPrinter printer)
}
internal static DbQueryCommandTree FromValidExpression(MetadataWorkspace metadata, DataSpace dataSpace, DbExpression query,
- bool useDatabaseNullSemantics)
+ bool useDatabaseNullSemantics, bool disableFilterOverProjectionSimplificationForCustomFunctions)
{
return new DbQueryCommandTree(metadata, dataSpace, query,
#if DEBUG
@@ -155,7 +183,8 @@ internal static DbQueryCommandTree FromValidExpression(MetadataWorkspace metadat
#else
false,
#endif
- useDatabaseNullSemantics);
+ useDatabaseNullSemantics,
+ disableFilterOverProjectionSimplificationForCustomFunctions);
}
}
}
diff --git a/src/EntityFramework/Core/Common/CommandTrees/Internal/ViewSimplifier.cs b/src/EntityFramework/Core/Common/CommandTrees/Internal/ViewSimplifier.cs
index 009a857c9c..cddd619b74 100644
--- a/src/EntityFramework/Core/Common/CommandTrees/Internal/ViewSimplifier.cs
+++ b/src/EntityFramework/Core/Common/CommandTrees/Internal/ViewSimplifier.cs
@@ -58,7 +58,7 @@ private DbQueryCommandTree Simplify(DbQueryCommandTree view)
queryExpression = simplifier(queryExpression);
view = DbQueryCommandTree.FromValidExpression(
- view.MetadataWorkspace, view.DataSpace, queryExpression, view.UseDatabaseNullSemantics);
+ view.MetadataWorkspace, view.DataSpace, queryExpression, view.UseDatabaseNullSemantics, view.DisableFilterOverProjectionSimplificationForCustomFunctions);
return view;
}
diff --git a/src/EntityFramework/Core/Common/DbProviderServices.cs b/src/EntityFramework/Core/Common/DbProviderServices.cs
index cfbc91bc0a..6630958be4 100644
--- a/src/EntityFramework/Core/Common/DbProviderServices.cs
+++ b/src/EntityFramework/Core/Common/DbProviderServices.cs
@@ -39,6 +39,7 @@ private static readonly ConcurrentDictionary
/// Constructs an EF provider that will use the obtained from
/// the app domain Singleton for resolving EF dependencies such
diff --git a/src/EntityFramework/Core/Common/EntitySql/SemanticAnalyzer.cs b/src/EntityFramework/Core/Common/EntitySql/SemanticAnalyzer.cs
index eeac2b810f..24c941e3bc 100644
--- a/src/EntityFramework/Core/Common/EntitySql/SemanticAnalyzer.cs
+++ b/src/EntityFramework/Core/Common/EntitySql/SemanticAnalyzer.cs
@@ -254,7 +254,7 @@ private static ParseResult ConvertQueryStatementToDbCommandTree(Statement astSta
return new ParseResult(
DbQueryCommandTree.FromValidExpression(
sr.TypeResolver.Perspective.MetadataWorkspace, sr.TypeResolver.Perspective.TargetDataspace, converted,
- useDatabaseNullSemantics: true),
+ useDatabaseNullSemantics: true, disableFilterOverProjectionSimplificationForCustomFunctions: false),
functionDefs);
}
diff --git a/src/EntityFramework/Core/Mapping/FunctionImportMappingComposable.cs b/src/EntityFramework/Core/Mapping/FunctionImportMappingComposable.cs
index cae83e32f8..cad9748942 100644
--- a/src/EntityFramework/Core/Mapping/FunctionImportMappingComposable.cs
+++ b/src/EntityFramework/Core/Mapping/FunctionImportMappingComposable.cs
@@ -403,7 +403,7 @@ internal DbQueryCommandTree GenerateFunctionView(out DiscriminatorMap discrimina
// Generate parameterized command, where command parameters are semantically the c-space function parameters.
return DbQueryCommandTree.FromValidExpression(
_containerMapping.StorageMappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, queryExpression,
- useDatabaseNullSemantics: true);
+ useDatabaseNullSemantics: true, disableFilterOverProjectionSimplificationForCustomFunctions: false);
}
private IEnumerable GetParametersForTargetFunctionCall()
diff --git a/src/EntityFramework/Core/Mapping/ViewGeneration/CqlGenerator.cs b/src/EntityFramework/Core/Mapping/ViewGeneration/CqlGenerator.cs
index defbb4492b..7774f3c6f8 100644
--- a/src/EntityFramework/Core/Mapping/ViewGeneration/CqlGenerator.cs
+++ b/src/EntityFramework/Core/Mapping/ViewGeneration/CqlGenerator.cs
@@ -107,7 +107,7 @@ internal DbQueryCommandTree GenerateCqt()
return DbQueryCommandTree.FromValidExpression(
m_mappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, query,
- useDatabaseNullSemantics: true);
+ useDatabaseNullSemantics: true, disableFilterOverProjectionSimplificationForCustomFunctions: false);
}
//
diff --git a/src/EntityFramework/Core/Objects/ELinq/CompiledELinqQueryState.cs b/src/EntityFramework/Core/Objects/ELinq/CompiledELinqQueryState.cs
index e035c19a24..fa556a57c0 100644
--- a/src/EntityFramework/Core/Objects/ELinq/CompiledELinqQueryState.cs
+++ b/src/EntityFramework/Core/Objects/ELinq/CompiledELinqQueryState.cs
@@ -57,6 +57,7 @@ internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMerg
ObjectQueryExecutionPlan plan = null;
var cacheEntry = _cacheEntry;
var useCSharpNullComparisonBehavior = ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior;
+ var disableFilterOverProjectionSimplificationForCustomFunctions = ObjectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions;
if (cacheEntry != null)
{
// The cache entry has already been retrieved, so compute the effective merge option with the following precedence:
@@ -77,7 +78,7 @@ internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMerg
// Prepare the execution plan using the command tree and the computed effective merge option
var tree = DbQueryCommandTree.FromValidExpression(
- ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !useCSharpNullComparisonBehavior);
+ ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !useCSharpNullComparisonBehavior, disableFilterOverProjectionSimplificationForCustomFunctions);
plan = _objectQueryExecutionPlanFactory.Prepare(
ObjectContext, tree, ElementType, mergeOption, EffectiveStreamingBehavior, converter.PropagatedSpan, parameters,
converter.AliasGenerator);
@@ -110,7 +111,7 @@ internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMerg
var queryExpression = converter.Convert();
var parameters = converter.GetParameters();
var tree = DbQueryCommandTree.FromValidExpression(
- ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !useCSharpNullComparisonBehavior);
+ ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !useCSharpNullComparisonBehavior, disableFilterOverProjectionSimplificationForCustomFunctions);
// If a cache entry for this compiled query's cache key was not successfully retrieved, then it must be created now.
// Note that this is only possible after converting the LINQ expression and discovering the propagated merge option,
diff --git a/src/EntityFramework/Core/Objects/ELinq/ELinqQueryState.cs b/src/EntityFramework/Core/Objects/ELinq/ELinqQueryState.cs
index 5462682bee..9aef511b03 100644
--- a/src/EntityFramework/Core/Objects/ELinq/ELinqQueryState.cs
+++ b/src/EntityFramework/Core/Objects/ELinq/ELinqQueryState.cs
@@ -25,6 +25,7 @@ internal class ELinqQueryState : ObjectQueryState
private Func _recompileRequired;
private IEnumerable> _linqParameters;
private bool _useCSharpNullComparisonBehavior;
+ private bool _disableFilterOverProjectionSimplificationForCustomFunctions;
private readonly ObjectQueryExecutionPlanFactory _objectQueryExecutionPlanFactory;
#endregion
@@ -53,6 +54,7 @@ internal ELinqQueryState(
_expression = expression;
_useCSharpNullComparisonBehavior = context.ContextOptions.UseCSharpNullComparisonBehavior;
+ _disableFilterOverProjectionSimplificationForCustomFunctions = context.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions;
_objectQueryExecutionPlanFactory = objectQueryExecutionPlanFactory ?? new ObjectQueryExecutionPlanFactory();
}
@@ -102,7 +104,8 @@ internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMerg
if ((explicitMergeOption.HasValue &&
explicitMergeOption.Value != plan.MergeOption)
|| _recompileRequired()
- || ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior != _useCSharpNullComparisonBehavior)
+ || ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior != _useCSharpNullComparisonBehavior
+ || ObjectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions != _disableFilterOverProjectionSimplificationForCustomFunctions)
{
plan = null;
}
@@ -133,6 +136,7 @@ internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMerg
converter.PropagatedMergeOption);
_useCSharpNullComparisonBehavior = ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior;
+ _disableFilterOverProjectionSimplificationForCustomFunctions = ObjectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions;
// If parameters were aggregated from referenced (non-LINQ) ObjectQuery instances then add them to the parameters collection
_linqParameters = converter.GetParameters();
@@ -186,7 +190,7 @@ internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMerg
if (plan == null)
{
var tree = DbQueryCommandTree.FromValidExpression(
- ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !_useCSharpNullComparisonBehavior);
+ ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression, !_useCSharpNullComparisonBehavior, _disableFilterOverProjectionSimplificationForCustomFunctions);
plan = _objectQueryExecutionPlanFactory.Prepare(
ObjectContext, tree, ElementType, mergeOption, EffectiveStreamingBehavior, converter.PropagatedSpan, null,
converter.AliasGenerator);
diff --git a/src/EntityFramework/Core/Objects/Internal/EntitySqlQueryState.cs b/src/EntityFramework/Core/Objects/Internal/EntitySqlQueryState.cs
index bf31a4051a..bb93072508 100644
--- a/src/EntityFramework/Core/Objects/Internal/EntitySqlQueryState.cs
+++ b/src/EntityFramework/Core/Objects/Internal/EntitySqlQueryState.cs
@@ -186,7 +186,7 @@ internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMerg
Debug.Assert(queryExpression != null, "EntitySqlQueryState.Parse returned null expression?");
var tree = DbQueryCommandTree.FromValidExpression(
ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression,
- useDatabaseNullSemantics: true);
+ useDatabaseNullSemantics: true, disableFilterOverProjectionSimplificationForCustomFunctions: false);
plan = _objectQueryExecutionPlanFactory.Prepare(
ObjectContext, tree, ElementType, mergeOption, EffectiveStreamingBehavior, Span, null,
DbExpressionBuilder.AliasGenerator);
diff --git a/src/EntityFramework/Core/Objects/Internal/ObjectQueryExecutionPlanFactory.cs b/src/EntityFramework/Core/Objects/Internal/ObjectQueryExecutionPlanFactory.cs
index 034e2a8973..c10dd0ced1 100644
--- a/src/EntityFramework/Core/Objects/Internal/ObjectQueryExecutionPlanFactory.cs
+++ b/src/EntityFramework/Core/Objects/Internal/ObjectQueryExecutionPlanFactory.cs
@@ -35,7 +35,7 @@ public virtual ObjectQueryExecutionPlan Prepare(
if (ObjectSpanRewriter.TryRewrite(tree, span, mergeOption, aliasGenerator, out spannedQuery, out spanInfo))
{
tree = DbQueryCommandTree.FromValidExpression(
- tree.MetadataWorkspace, tree.DataSpace, spannedQuery, tree.UseDatabaseNullSemantics);
+ tree.MetadataWorkspace, tree.DataSpace, spannedQuery, tree.UseDatabaseNullSemantics, tree.DisableFilterOverProjectionSimplificationForCustomFunctions);
}
else
{
diff --git a/src/EntityFramework/Core/Objects/ObjectContext.cs b/src/EntityFramework/Core/Objects/ObjectContext.cs
index 82ab777285..3f0760cf47 100644
--- a/src/EntityFramework/Core/Objects/ObjectContext.cs
+++ b/src/EntityFramework/Core/Objects/ObjectContext.cs
@@ -2869,7 +2869,7 @@ internal virtual Tuple PrepareRefreshQuery(
// Initialize the command tree used to issue the refresh query.
var tree = DbQueryCommandTree.FromValidExpression(
- MetadataWorkspace, DataSpace.CSpace, refreshQuery, useDatabaseNullSemantics: true);
+ MetadataWorkspace, DataSpace.CSpace, refreshQuery, useDatabaseNullSemantics: true, disableFilterOverProjectionSimplificationForCustomFunctions: false);
// Evaluate the refresh query using ObjectQuery and process the results to update the ObjectStateManager.
var mergeOption = (RefreshMode.StoreWins == refreshMode
diff --git a/src/EntityFramework/Core/Objects/ObjectContextOptions.cs b/src/EntityFramework/Core/Objects/ObjectContextOptions.cs
index 309ad8d094..4e657adc97 100644
--- a/src/EntityFramework/Core/Objects/ObjectContextOptions.cs
+++ b/src/EntityFramework/Core/Objects/ObjectContextOptions.cs
@@ -62,5 +62,7 @@ internal ObjectContextOptions()
///
/// true if the C# NullComparison behavior should be used; otherwise, false.
public bool UseCSharpNullComparisonBehavior { get; set; }
+
+ public bool DisableFilterOverProjectionSimplificationForCustomFunctions { get; set; }
}
}
diff --git a/src/EntityFramework/Core/Query/PlanCompiler/CTreeGenerator.cs b/src/EntityFramework/Core/Query/PlanCompiler/CTreeGenerator.cs
index 1adc02f3a9..1ee8b5bb12 100644
--- a/src/EntityFramework/Core/Query/PlanCompiler/CTreeGenerator.cs
+++ b/src/EntityFramework/Core/Query/PlanCompiler/CTreeGenerator.cs
@@ -370,7 +370,7 @@ private CTreeGenerator(Command itree, Node toConvert)
// Create the query command tree using database null semantics because this class is only
// used during the CodeGen phase which occurs after the NullSemantics phase of plan compiler.
_queryTree = DbQueryCommandTree.FromValidExpression(
- itree.MetadataWorkspace, DataSpace.SSpace, queryExpression, useDatabaseNullSemantics: true);
+ itree.MetadataWorkspace, DataSpace.SSpace, queryExpression, useDatabaseNullSemantics: true, disableFilterOverProjectionSimplificationForCustomFunctions: false);
}
#endregion
diff --git a/src/EntityFramework/Core/Query/PlanCompiler/FilterOpRules.cs b/src/EntityFramework/Core/Query/PlanCompiler/FilterOpRules.cs
index 7e300d9d5d..1ff1d0a2ae 100644
--- a/src/EntityFramework/Core/Query/PlanCompiler/FilterOpRules.cs
+++ b/src/EntityFramework/Core/Query/PlanCompiler/FilterOpRules.cs
@@ -3,6 +3,7 @@
namespace System.Data.Entity.Core.Query.PlanCompiler
{
using System.Collections.Generic;
+ using System.Data.Entity.Core.Common;
using System.Data.Entity.Core.Query.InternalTrees;
using System.Diagnostics.CodeAnalysis;
@@ -144,6 +145,12 @@ private static bool ProcessFilterOverProject(RuleProcessingContext context, Node
return false;
}
+ // check to see that this predicate doesn't reference user-defined functions
+ if (trc.IncludeCustomFunctionOp(predicateNode, varMap))
+ {
+ return false;
+ }
+
//
// Try to remap the predicate in terms of the definitions of the Vars
//
diff --git a/src/EntityFramework/Core/Query/PlanCompiler/PlanCompiler.cs b/src/EntityFramework/Core/Query/PlanCompiler/PlanCompiler.cs
index 62d4c677e9..9f1f11758b 100644
--- a/src/EntityFramework/Core/Query/PlanCompiler/PlanCompiler.cs
+++ b/src/EntityFramework/Core/Query/PlanCompiler/PlanCompiler.cs
@@ -181,6 +181,11 @@ internal ConstraintManager ConstraintManager
}
}
+ internal bool DisableFilterOverProjectionSimplificationForCustomFunctions
+ {
+ get { return m_ctree.DisableFilterOverProjectionSimplificationForCustomFunctions; }
+ }
+
#if DEBUG
///
/// Get the current plan compiler phase
diff --git a/src/EntityFramework/Core/Query/PlanCompiler/TransformationRulesContext.cs b/src/EntityFramework/Core/Query/PlanCompiler/TransformationRulesContext.cs
index a29fc804ab..9a5b5e52ca 100644
--- a/src/EntityFramework/Core/Query/PlanCompiler/TransformationRulesContext.cs
+++ b/src/EntityFramework/Core/Query/PlanCompiler/TransformationRulesContext.cs
@@ -262,6 +262,52 @@ internal bool IsScalarOpTree(Node node, Dictionary varRefMap)
return IsScalarOpTree(node, varRefMap, ref nodeCount);
}
+ ///
+ /// Is this tree uses user-defined functions
+ /// Simplifing query with UDFs could caused to suboptimal plans
+ ///
+ /// Current subtree to process
+ /// Mapped variables
+ ///
+ internal bool IncludeCustomFunctionOp(Node node, Dictionary varMap)
+ {
+ if (!m_compilerState.DisableFilterOverProjectionSimplificationForCustomFunctions)
+ {
+ return false;
+ }
+
+ PlanCompiler.Assert(varMap != null, "Null varRef map");
+
+ if (node.Op.OpType == OpType.VarRef)
+ {
+ var varRefOp = (VarRefOp)node.Op;
+ if (varMap.TryGetValue(varRefOp.Var, out var newNode))
+ {
+ return IncludeCustomFunctionOp(newNode, varMap);
+ }
+ }
+
+ if (node.Op.OpType == OpType.Function)
+ {
+ var functionOp = node.Op as FunctionOp;
+ if (!functionOp.Function.BuiltInAttribute)
+ {
+ return true;
+ }
+ }
+
+ // Simply process the result of the children.
+ for (var i = 0; i < node.Children.Count; i++)
+ {
+ if (IncludeCustomFunctionOp(node.Children[i], varMap))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
//
// Get a mapping from Var->Expression for a VarDefListOp tree. This information
// will be used by later stages to replace all references to the Vars by the
diff --git a/src/EntityFramework/Infrastructure/DbContextConfiguration.cs b/src/EntityFramework/Infrastructure/DbContextConfiguration.cs
index 75cd54b1de..ac93e11b5c 100644
--- a/src/EntityFramework/Infrastructure/DbContextConfiguration.cs
+++ b/src/EntityFramework/Infrastructure/DbContextConfiguration.cs
@@ -137,6 +137,20 @@ public bool UseDatabaseNullSemantics
set { _internalContext.UseDatabaseNullSemantics = value; }
}
+ ///
+ /// By default expression like
+ /// .Select(x => NewProperty = func(x.Property)).Where(x => x.NewProperty == ...)
+ /// are simplified to avoid nested SELECT
+ /// In some cases, simplifing query with UDFs could caused to suboptimal plans due to calling UDF twice.
+ /// Also some SQL functions aren't allow in WHERE clause.
+ /// Disabling that behavior
+ ///
+ public bool DisableFilterOverProjectionSimplificationForCustomFunctions
+ {
+ get { return _internalContext.DisableFilterOverProjectionSimplificationForCustomFunctions; }
+ set { _internalContext.DisableFilterOverProjectionSimplificationForCustomFunctions = value; }
+ }
+
///
/// Gets or sets a value indicating whether the
/// method is called automatically by methods of and related classes.
diff --git a/src/EntityFramework/Internal/EagerInternalContext.cs b/src/EntityFramework/Internal/EagerInternalContext.cs
index 9ffdf80580..659c1dd6c5 100644
--- a/src/EntityFramework/Internal/EagerInternalContext.cs
+++ b/src/EntityFramework/Internal/EagerInternalContext.cs
@@ -248,6 +248,12 @@ public override bool UseDatabaseNullSemantics
set { ObjectContextInUse.ContextOptions.UseCSharpNullComparisonBehavior = !value; }
}
+ public override bool DisableFilterOverProjectionSimplificationForCustomFunctions
+ {
+ get { return !ObjectContextInUse.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions; }
+ set { ObjectContextInUse.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions = !value; }
+ }
+
public override int? CommandTimeout
{
get { return ObjectContextInUse.CommandTimeout; }
diff --git a/src/EntityFramework/Internal/InternalContext.cs b/src/EntityFramework/Internal/InternalContext.cs
index 56e559e671..81fa5fbb69 100644
--- a/src/EntityFramework/Internal/InternalContext.cs
+++ b/src/EntityFramework/Internal/InternalContext.cs
@@ -610,6 +610,16 @@ private Action CreateInitializationAction(IDatabaseInitializer
public abstract bool UseDatabaseNullSemantics { get; set; }
+ ///
+ /// By default expression like
+ /// .Select(x => NewProperty = func(x.Property)).Where(x => x.NewProperty == ...)
+ /// are simplified to avoid nested SELECT
+ /// In some cases, simplifing query with UDFs could caused to suboptimal plans due to calling UDF twice.
+ /// Also some SQL functions aren't allow in WHERE clause.
+ /// Disabling that behavior
+ ///
+ public abstract bool DisableFilterOverProjectionSimplificationForCustomFunctions { get; set; }
+
public abstract int? CommandTimeout { get; set; }
//
diff --git a/src/EntityFramework/Internal/LazyInternalContext.cs b/src/EntityFramework/Internal/LazyInternalContext.cs
index c1cf15edb6..aeda0142a1 100644
--- a/src/EntityFramework/Internal/LazyInternalContext.cs
+++ b/src/EntityFramework/Internal/LazyInternalContext.cs
@@ -463,6 +463,7 @@ var model
_objectContext.ContextOptions.LazyLoadingEnabled = _initialLazyLoadingFlag;
_objectContext.ContextOptions.ProxyCreationEnabled = _initialProxyCreationFlag;
_objectContext.ContextOptions.UseCSharpNullComparisonBehavior = !_useDatabaseNullSemanticsFlag;
+ _objectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions = _disableFilterOverProjectionSimplificationForCustomFunctions;
_objectContext.CommandTimeout = _commandTimeout;
_objectContext.ContextOptions.UseConsistentNullReferenceBehavior = true;
@@ -804,6 +805,31 @@ public override bool UseDatabaseNullSemantics
}
}
+ private bool _disableFilterOverProjectionSimplificationForCustomFunctions;
+
+ public override bool DisableFilterOverProjectionSimplificationForCustomFunctions
+ {
+ get
+ {
+ var objectContext = ObjectContextInUse;
+ return objectContext != null
+ ? !objectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions
+ : _disableFilterOverProjectionSimplificationForCustomFunctions;
+ }
+ set
+ {
+ var objectContext = ObjectContextInUse;
+ if (objectContext != null)
+ {
+ objectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions = !value;
+ }
+ else
+ {
+ _disableFilterOverProjectionSimplificationForCustomFunctions = value;
+ }
+ }
+ }
+
public override int? CommandTimeout
{
get
diff --git a/src/EntityFramework/Internal/MockingProxies/ObjectContextProxy.cs b/src/EntityFramework/Internal/MockingProxies/ObjectContextProxy.cs
index 42306297f0..a6f9a3ecb0 100644
--- a/src/EntityFramework/Internal/MockingProxies/ObjectContextProxy.cs
+++ b/src/EntityFramework/Internal/MockingProxies/ObjectContextProxy.cs
@@ -91,6 +91,7 @@ public virtual void CopyContextOptions(ObjectContextProxy source)
_objectContext.ContextOptions.UseLegacyPreserveChangesBehavior =
source._objectContext.ContextOptions.UseLegacyPreserveChangesBehavior;
_objectContext.CommandTimeout = source._objectContext.CommandTimeout;
+ _objectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions = source._objectContext.ContextOptions.DisableFilterOverProjectionSimplificationForCustomFunctions;
_objectContext.InterceptionContext = source._objectContext.InterceptionContext.WithObjectContext(_objectContext);
}
diff --git a/test/EntityFramework/FunctionalTests/Query/FilterOpRulesTests.cs b/test/EntityFramework/FunctionalTests/Query/FilterOpRulesTests.cs
index 2edad2a823..a2645d8d1a 100644
--- a/test/EntityFramework/FunctionalTests/Query/FilterOpRulesTests.cs
+++ b/test/EntityFramework/FunctionalTests/Query/FilterOpRulesTests.cs
@@ -2,6 +2,10 @@
namespace System.Data.Entity.Query
{
+ using System.Data.Entity.Core.Common;
+ using System.Data.Entity.Core.Metadata.Edm;
+ using System.Data.Entity.Infrastructure;
+ using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using Xunit;
@@ -30,6 +34,47 @@ static BlogContext()
{
Database.SetInitializer(null);
}
+
+ protected override void OnModelCreating(DbModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Conventions.Add(new CustomFunction());
+ }
+ }
+
+ public static class CustomFunctions
+ {
+ [DbFunction("SqlServer", "MyCustomFunc")]
+ public static int MyCustomFunc(string value)
+ {
+ throw new NotSupportedException("Direct calls are not supported.");
+ }
+ }
+
+ public class CustomFunction : IConvention, IStoreModelConvention
+ {
+ public void Apply(EntityContainer item, DbModel model)
+ {
+ var customFuncStore = EdmFunction.Create("MyCustomFunc", "SqlServer", DataSpace.SSpace, new EdmFunctionPayload
+ {
+ ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion,
+ IsComposable = true,
+ IsAggregate = false,
+ StoreFunctionName = "MyCustomFunc",
+ IsBuiltIn = false,
+ ReturnParameters = new[]
+ {
+ FunctionParameter.Create("ReturnType", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32), ParameterMode.ReturnValue)
+ },
+ Parameters = new[]
+ {
+ FunctionParameter.Create("input", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), ParameterMode.In),
+ }
+ }, null);
+
+
+ model.StoreModel.AddItem(customFuncStore);
+ }
}
[Fact]
@@ -120,9 +165,9 @@ FROM [dbo].[BlogEntries] AS [Extent2]
context.Configuration.UseDatabaseNullSemantics = false;
var query = from b in context.Blogs
- from e in context.BlogEntries.Where(e => e.Name == b.Name).Take(1).DefaultIfEmpty()
- where b.Name == e.Name
- select b;
+ from e in context.BlogEntries.Where(e => e.Name == b.Name).Take(1).DefaultIfEmpty()
+ where b.Name == e.Name
+ select b;
QueryTestHelpers.VerifyDbQuery(query, expectedSql);
}
@@ -179,5 +224,98 @@ from e in context.BlogEntries.Where(e => e.Name == b.Name).Take(1).DefaultIfEmpt
QueryTestHelpers.VerifyDbQuery(query, expectedSql);
}
}
+
+ [Fact]
+ public void Rule_FilterOverProject_promotes_to_single_Select_if_builtint_function()
+ {
+ var expectedSql =
+@"SELECT
+ [Extent1].[Id] AS [Id],
+ CAST(LEN([Extent1].[Name]) AS int) AS [C1]
+ FROM [dbo].[Blogs] AS [Extent1]
+ WHERE (CAST(LEN([Extent1].[Name]) AS int)) > 10";
+
+ using (var context = new BlogContext())
+ {
+ context.Configuration.UseDatabaseNullSemantics = true;
+
+ var query = context.Blogs.Select(b => new { b.Id, Len = b.Name.Length }).Where(b => b.Len > 10);
+
+ QueryTestHelpers.VerifyDbQuery(query, expectedSql);
+ }
+ }
+
+ [Fact]
+ public void Rule_FilterOverProject_does_not_promote_to_single_Select_if_custom_function_and_does_opt_in()
+ {
+ var expectedSql =
+@"SELECT
+ [Project1].[Id] AS [Id],
+ [Project1].[C1] AS [C1]
+ FROM ( SELECT
+ [Extent1].[Id] AS [Id],
+ [SqlServer].[MyCustomFunc]([Extent1].[Name]) AS [C1]
+ FROM [dbo].[Blogs] AS [Extent1]
+ ) AS [Project1]
+ WHERE ([Project1].[Id] > 10) AND ([Project1].[C1] > 10)";
+
+ using (var context = new BlogContext())
+ {
+ context.Configuration.UseDatabaseNullSemantics = true;
+ context.Configuration.DisableFilterOverProjectionSimplificationForCustomFunctions = true;
+
+ var query = context.Blogs.Select(b => new { b.Id, Len = CustomFunctions.MyCustomFunc(b.Name) }).Where(b => b.Id > 10 && b.Len > 10);
+ QueryTestHelpers.VerifyDbQuery(query, expectedSql);
+ }
+ }
+
+ [Fact]
+ public void Rule_FilterOverProject_does_not_promote_to_single_Select_with_limit_if_custom_function_and_does_opt_in()
+ {
+ var expectedSql =
+@"SELECT TOP (10)
+ [Project1].[Id] AS [Id],
+ [Project1].[C1] AS [C1]
+ FROM ( SELECT
+ [Extent1].[Id] AS [Id],
+ [SqlServer].[MyCustomFunc]([Extent1].[Name]) AS [C1]
+ FROM [dbo].[Blogs] AS [Extent1]
+ ) AS [Project1]
+ WHERE ([Project1].[Id] > 10) AND ([Project1].[C1] > 10)
+ ORDER BY [Project1].[Id] ASC";
+
+ using (var context = new BlogContext())
+ {
+ context.Configuration.UseDatabaseNullSemantics = true;
+ context.Configuration.DisableFilterOverProjectionSimplificationForCustomFunctions = true;
+
+ var query = context.Blogs.Select(b => new { b.Id, Len = CustomFunctions.MyCustomFunc(b.Name) }).Where(b => b.Id > 10 && b.Len > 10).OrderBy(b => b.Id).Take(10);
+ QueryTestHelpers.VerifyDbQuery(query, expectedSql);
+ }
+ }
+
+
+ [Fact]
+ public void Rule_FilterOverProject_does_promote_to_single_Select_if_custom_function_and_doesnt_opt_in()
+ {
+ var expectedSql =
+@"SELECT
+ [Extent1].[Id] AS [Id],
+ [SqlServer].[MyCustomFunc]([Extent1].[Name]) AS [C1]
+ FROM [dbo].[Blogs] AS [Extent1]
+ WHERE ([SqlServer].[MyCustomFunc]([Extent1].[Name])) > 10";
+
+ using (var context = new BlogContext())
+ {
+ context.Configuration.UseDatabaseNullSemantics = true;
+ context.Configuration.DisableFilterOverProjectionSimplificationForCustomFunctions = false; // false is default, but using explicit valueto make it obvious
+
+ var query = context.Blogs.Select(b => new { b.Id, Len = CustomFunctions.MyCustomFunc(b.Name) }).Where(b => b.Len > 10);
+ QueryTestHelpers.VerifyDbQuery(query, expectedSql);
+ }
+ }
+
+
+
}
}