43ca97936772fbcb33395f360dc920196399c5e1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / Internal / ObjectQueryExecutionPlan.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ObjectQueryExecutionPlan.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  Microsoft
7 //---------------------------------------------------------------------
8
9 namespace System.Data.Objects.Internal
10 {
11     using System;
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>>;
22     
23     /// <summary>
24     /// Represents the 'compiled' form of all elements (query + result assembly) required to execute a specific <see cref="ObjectQuery"/>
25     /// </summary>
26     internal sealed class ObjectQueryExecutionPlan
27     {
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;
33         
34         /// <summary>If the query yields entities from a single entity set, the value is stored here.</summary>
35         private readonly EntitySet _singleEntitySet;
36
37         private ObjectQueryExecutionPlan(DbCommandDefinition commandDefinition, ShaperFactory resultShaperFactory, TypeUsage resultType, MergeOption mergeOption, EntitySet singleEntitySet, CompiledQueryParameters compiledQueryParameters)
38         {
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");
42
43             this.CommandDefinition = commandDefinition;
44             this.ResultShaperFactory = resultShaperFactory;
45             this.ResultType = resultType;
46             this.MergeOption = mergeOption;
47             this._singleEntitySet = singleEntitySet;
48             this.CompiledQueryParameters = compiledQueryParameters;
49         }
50
51         internal static ObjectQueryExecutionPlan Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Span span, CompiledQueryParameters compiledQueryParameters, AliasGenerator aliasGenerator)
52         {
53             TypeUsage treeResultType = tree.Query.ResultType;
54
55             // Rewrite this tree for Span?
56             DbExpression spannedQuery = null;
57             SpanIndex spanInfo;
58             if (ObjectSpanRewriter.TryRewrite(tree, span, mergeOption, aliasGenerator, out spannedQuery, out spanInfo))
59             {
60                 tree = DbQueryCommandTree.FromValidExpression(tree.MetadataWorkspace, tree.DataSpace, spannedQuery);
61             }
62             else
63             {
64                 spanInfo = null;
65             }
66
67             DbConnection connection = context.Connection;
68             DbCommandDefinition definition = null;
69
70             // The connection is required to get to the CommandDefinition builder.
71             if (connection == null)
72             {
73                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_InvalidConnection);
74             }
75                         
76             DbProviderServices services = DbProviderServices.GetProviderServices(connection);
77
78             try
79             {
80                 definition = services.CreateCommandDefinition(tree);
81             }
82             catch (EntityCommandCompilationException)
83             {
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
86                 // here instead.
87                 throw;
88             }
89             catch (Exception e)
90             {
91                 // we should not be wrapping all exceptions
92                 if (EntityUtil.IsCatchableExceptionType(e))
93                 {
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);
98                 }
99                 throw;
100             }
101
102             if (definition == null)
103             {
104                 throw EntityUtil.ProviderDoesNotSupportCommandTrees();
105             }
106
107             EntityCommandDefinition entityDefinition = (EntityCommandDefinition)definition;
108             QueryCacheManager cacheManager = context.Perspective.MetadataWorkspace.GetQueryCacheManager();
109             
110             ShaperFactory shaperFactory = ShaperFactory.Create(elementType, cacheManager, entityDefinition.CreateColumnMap(null),
111                 context.MetadataWorkspace, spanInfo, mergeOption, false);
112
113             // attempt to determine entity information for this query (e.g. which entity type and which entity set)
114             //EntityType rootEntityType = null;
115
116             EntitySet singleEntitySet = null;
117
118             if (treeResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
119             {
120                 // determine if the entity set is unambiguous given the entity type
121                 if (null != entityDefinition.EntitySets)
122                 {
123                     foreach (EntitySet entitySet in entityDefinition.EntitySets)
124                     {
125                         if (null != entitySet)
126                         {
127                             if (entitySet.ElementType.IsAssignableFrom(((CollectionType)treeResultType.EdmType).TypeUsage.EdmType))
128                             {
129                                 if (singleEntitySet == null)
130                                 {
131                                     // found a single match
132                                     singleEntitySet = entitySet;
133                                 }
134                                 else
135                                 {
136                                     // there's more than one matching entity set
137                                     singleEntitySet = null;
138                                     break;
139                                 }
140                             }
141                         }
142                     }
143                 }
144             }
145
146             return new ObjectQueryExecutionPlan(definition, shaperFactory, treeResultType, mergeOption, singleEntitySet, compiledQueryParameters);
147         }
148
149         internal string ToTraceString()
150         {
151             string traceString = string.Empty;
152             EntityCommandDefinition entityCommandDef = this.CommandDefinition as EntityCommandDefinition;
153             if (entityCommandDef != null)
154             {
155                 traceString = entityCommandDef.ToTraceString();
156             }
157             return traceString;
158         }
159
160         internal ObjectResult<TResultType> Execute<TResultType>(ObjectContext context, ObjectParameterCollection parameterValues)
161         {
162             DbDataReader storeReader = null;
163             try
164             {
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);
168
169                 // pass through parameters and timeout values
170                 if (context.CommandTimeout.HasValue)
171                 {
172                     entityCommand.CommandTimeout = context.CommandTimeout.Value;
173                 }
174
175                 if (parameterValues != null)
176                 {
177                     foreach (ObjectParameter parameter in parameterValues)
178                     {
179                         int index = entityCommand.Parameters.IndexOf(parameter.Name);
180
181                         if (index != -1)
182                         {
183                             entityCommand.Parameters[index].Value = parameter.Value ?? DBNull.Value;
184                         }
185                     }
186                 }
187
188                 // acquire store reader
189                 storeReader = commandDefinition.ExecuteStoreCommands(entityCommand, CommandBehavior.Default);
190
191                 ShaperFactory<TResultType> shaperFactory = (ShaperFactory<TResultType>)this.ResultShaperFactory;
192                 Shaper<TResultType> shaper = shaperFactory.Create(storeReader, context, context.MetadataWorkspace, this.MergeOption, true);
193
194                 // create materializer delegate
195                 TypeUsage resultItemEdmType;
196
197                 if (ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
198                 {
199                     resultItemEdmType = ((CollectionType)ResultType.EdmType).TypeUsage;
200                 }
201                 else
202                 {
203                     resultItemEdmType = ResultType;
204                 }
205
206                 return new ObjectResult<TResultType>(shaper, this._singleEntitySet, resultItemEdmType);
207             }
208             catch (Exception)
209             {
210                 if (null != storeReader)
211                 {
212                     // Note: The caller is responsible for disposing reader if creating
213                     // the enumerator fails.
214                     storeReader.Dispose();
215                 }
216                 throw;
217             }
218         }
219
220         internal static ObjectResult<TResultType> ExecuteCommandTree<TResultType>(ObjectContext context, DbQueryCommandTree query, MergeOption mergeOption)
221         {
222             Debug.Assert(context != null, "ObjectContext cannot be null");
223             Debug.Assert(query != null, "Command tree cannot be null");
224
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);
227         }
228     }
229 }