1 //---------------------------------------------------------------------
2 // <copyright file="ObjectQueryExecutionPlan.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 //---------------------------------------------------------------------
9 namespace System.Data.Objects.Internal
12 using System.Data.Common;
13 using System.Data.Common.CommandTrees;
14 using System.Data.Common.Internal.Materialization;
15 using System.Data.Common.QueryCache;
16 using System.Data.Common.Utils;
17 using System.Data.EntityClient;
18 using System.Data.Metadata.Edm;
19 using System.Data.Objects;
20 using System.Diagnostics;
21 using CompiledQueryParameters = System.Collections.ObjectModel.ReadOnlyCollection<System.Collections.Generic.KeyValuePair<ObjectParameter, System.Data.Objects.ELinq.QueryParameterExpression>>;
24 /// Represents the 'compiled' form of all elements (query + result assembly) required to execute a specific <see cref="ObjectQuery"/>
26 internal sealed class ObjectQueryExecutionPlan
28 internal readonly DbCommandDefinition CommandDefinition;
29 internal readonly ShaperFactory ResultShaperFactory;
30 internal readonly TypeUsage ResultType;
31 internal readonly MergeOption MergeOption;
32 internal readonly CompiledQueryParameters CompiledQueryParameters;
34 /// <summary>If the query yields entities from a single entity set, the value is stored here.</summary>
35 private readonly EntitySet _singleEntitySet;
37 private ObjectQueryExecutionPlan(DbCommandDefinition commandDefinition, ShaperFactory resultShaperFactory, TypeUsage resultType, MergeOption mergeOption, EntitySet singleEntitySet, CompiledQueryParameters compiledQueryParameters)
39 Debug.Assert(commandDefinition != null, "A command definition is required");
40 Debug.Assert(resultShaperFactory != null, "A result shaper factory is required");
41 Debug.Assert(resultType != null, "A result type is required");
43 this.CommandDefinition = commandDefinition;
44 this.ResultShaperFactory = resultShaperFactory;
45 this.ResultType = resultType;
46 this.MergeOption = mergeOption;
47 this._singleEntitySet = singleEntitySet;
48 this.CompiledQueryParameters = compiledQueryParameters;
51 internal static ObjectQueryExecutionPlan Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Span span, CompiledQueryParameters compiledQueryParameters, AliasGenerator aliasGenerator)
53 TypeUsage treeResultType = tree.Query.ResultType;
55 // Rewrite this tree for Span?
56 DbExpression spannedQuery = null;
58 if (ObjectSpanRewriter.TryRewrite(tree, span, mergeOption, aliasGenerator, out spannedQuery, out spanInfo))
60 tree = DbQueryCommandTree.FromValidExpression(tree.MetadataWorkspace, tree.DataSpace, spannedQuery);
67 DbConnection connection = context.Connection;
68 DbCommandDefinition definition = null;
70 // The connection is required to get to the CommandDefinition builder.
71 if (connection == null)
73 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_InvalidConnection);
76 DbProviderServices services = DbProviderServices.GetProviderServices(connection);
80 definition = services.CreateCommandDefinition(tree);
82 catch (EntityCommandCompilationException)
84 // If we're running against EntityCommand, we probably already caught the providers'
85 // exception and wrapped it, we don't want to do that again, so we'll just rethrow
91 // we should not be wrapping all exceptions
92 if (EntityUtil.IsCatchableExceptionType(e))
94 // we don't wan't folks to have to know all the various types of exceptions that can
95 // occur, so we just rethrow a CommandDefinitionException and make whatever we caught
96 // the inner exception of it.
97 throw EntityUtil.CommandCompilation(System.Data.Entity.Strings.EntityClient_CommandDefinitionPreparationFailed, e);
102 if (definition == null)
104 throw EntityUtil.ProviderDoesNotSupportCommandTrees();
107 EntityCommandDefinition entityDefinition = (EntityCommandDefinition)definition;
108 QueryCacheManager cacheManager = context.Perspective.MetadataWorkspace.GetQueryCacheManager();
110 ShaperFactory shaperFactory = ShaperFactory.Create(elementType, cacheManager, entityDefinition.CreateColumnMap(null),
111 context.MetadataWorkspace, spanInfo, mergeOption, false);
113 // attempt to determine entity information for this query (e.g. which entity type and which entity set)
114 //EntityType rootEntityType = null;
116 EntitySet singleEntitySet = null;
118 if (treeResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
120 // determine if the entity set is unambiguous given the entity type
121 if (null != entityDefinition.EntitySets)
123 foreach (EntitySet entitySet in entityDefinition.EntitySets)
125 if (null != entitySet)
127 if (entitySet.ElementType.IsAssignableFrom(((CollectionType)treeResultType.EdmType).TypeUsage.EdmType))
129 if (singleEntitySet == null)
131 // found a single match
132 singleEntitySet = entitySet;
136 // there's more than one matching entity set
137 singleEntitySet = null;
146 return new ObjectQueryExecutionPlan(definition, shaperFactory, treeResultType, mergeOption, singleEntitySet, compiledQueryParameters);
149 internal string ToTraceString()
151 string traceString = string.Empty;
152 EntityCommandDefinition entityCommandDef = this.CommandDefinition as EntityCommandDefinition;
153 if (entityCommandDef != null)
155 traceString = entityCommandDef.ToTraceString();
160 internal ObjectResult<TResultType> Execute<TResultType>(ObjectContext context, ObjectParameterCollection parameterValues)
162 DbDataReader storeReader = null;
165 // create entity command (just do this to snarf store command)
166 EntityCommandDefinition commandDefinition = (EntityCommandDefinition)this.CommandDefinition;
167 EntityCommand entityCommand = new EntityCommand((EntityConnection)context.Connection, commandDefinition);
169 // pass through parameters and timeout values
170 if (context.CommandTimeout.HasValue)
172 entityCommand.CommandTimeout = context.CommandTimeout.Value;
175 if (parameterValues != null)
177 foreach (ObjectParameter parameter in parameterValues)
179 int index = entityCommand.Parameters.IndexOf(parameter.Name);
183 entityCommand.Parameters[index].Value = parameter.Value ?? DBNull.Value;
188 // acquire store reader
189 storeReader = commandDefinition.ExecuteStoreCommands(entityCommand, CommandBehavior.Default);
191 ShaperFactory<TResultType> shaperFactory = (ShaperFactory<TResultType>)this.ResultShaperFactory;
192 Shaper<TResultType> shaper = shaperFactory.Create(storeReader, context, context.MetadataWorkspace, this.MergeOption, true);
194 // create materializer delegate
195 TypeUsage resultItemEdmType;
197 if (ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
199 resultItemEdmType = ((CollectionType)ResultType.EdmType).TypeUsage;
203 resultItemEdmType = ResultType;
206 return new ObjectResult<TResultType>(shaper, this._singleEntitySet, resultItemEdmType);
210 if (null != storeReader)
212 // Note: The caller is responsible for disposing reader if creating
213 // the enumerator fails.
214 storeReader.Dispose();
220 internal static ObjectResult<TResultType> ExecuteCommandTree<TResultType>(ObjectContext context, DbQueryCommandTree query, MergeOption mergeOption)
222 Debug.Assert(context != null, "ObjectContext cannot be null");
223 Debug.Assert(query != null, "Command tree cannot be null");
225 ObjectQueryExecutionPlan execPlan = ObjectQueryExecutionPlan.Prepare(context, query, typeof(TResultType), mergeOption, null, null, System.Data.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.AliasGenerator);
226 return execPlan.Execute<TResultType>(context, null);