Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / EntityClient / EntityCommandDefinition.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="EntityCommandDefinition.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  Microsoft
7 // @backupOwner Microsoft
8 //------------------------------------------------------------------------------
9
10 namespace System.Data.EntityClient {
11
12     using System.Collections.Generic;
13     using System.Data.Common;
14     using System.Data.Common.CommandTrees;
15     using System.Data.Common.Utils;
16     using System.Data.Mapping;
17     using System.Data.Metadata.Edm;
18     using System.Data.Query.InternalTrees;
19     using System.Data.Query.PlanCompiler;
20     using System.Data.Query.ResultAssembly;
21     using System.Diagnostics;
22     using System.Linq;
23     using System.Text;
24
25     /// <summary>
26     /// An aggregate Command Definition used by the EntityClient layers.  This is an aggregator
27     /// object that represent information from multiple underlying provider commands.
28     /// </summary>
29     sealed internal class EntityCommandDefinition : DbCommandDefinition {
30
31         #region internal state
32
33         /// <summary>
34         /// nested store command definitions
35         /// </summary>
36         private readonly List<DbCommandDefinition> _mappedCommandDefinitions;
37
38         /// <summary>
39         /// generates column map for the store result reader
40         /// </summary>
41         private readonly IColumnMapGenerator[] _columnMapGenerators;
42
43         /// <summary>
44         /// list of the parameters that the resulting command should have
45         /// </summary>
46         private readonly System.Collections.ObjectModel.ReadOnlyCollection<EntityParameter> _parameters;
47
48         /// <summary>
49         /// Set of entity sets exposed in the command.
50         /// </summary>
51         private readonly Set<EntitySet> _entitySets;
52
53         #endregion
54
55         #region constructors
56         /// <summary>
57         /// don't let this be constructed publicly;
58         /// </summary>
59         /// <exception cref="EntityCommandCompilationException">Cannot prepare the command definition for execution; consult the InnerException for more information.</exception>
60         /// <exception cref="NotSupportedException">The ADO.NET Data Provider you are using does not support CommandTrees.</exception>
61         internal EntityCommandDefinition(DbProviderFactory storeProviderFactory, DbCommandTree commandTree) {
62             EntityUtil.CheckArgumentNull(storeProviderFactory, "storeProviderFactory");
63             EntityUtil.CheckArgumentNull(commandTree, "commandTree");
64
65             DbProviderServices storeProviderServices = DbProviderServices.GetProviderServices(storeProviderFactory);
66
67             try {
68                 if (DbCommandTreeKind.Query == commandTree.CommandTreeKind) {
69                     // Next compile the plan for the command tree
70                     List<ProviderCommandInfo> mappedCommandList = new List<ProviderCommandInfo>();
71                     ColumnMap columnMap;
72                     int columnCount;
73                     PlanCompiler.Compile(commandTree, out mappedCommandList, out columnMap, out columnCount, out _entitySets);
74                     _columnMapGenerators = new IColumnMapGenerator[] {new ConstantColumnMapGenerator(columnMap, columnCount)};
75                     // Note: we presume that the first item in the ProviderCommandInfo is the root node;
76                     Debug.Assert(mappedCommandList.Count > 0, "empty providerCommandInfo collection and no exception?"); // this shouldn't ever happen.
77
78                     // Then, generate the store commands from the resulting command tree(s)
79                     _mappedCommandDefinitions = new List<DbCommandDefinition>(mappedCommandList.Count);
80
81                     foreach (ProviderCommandInfo providerCommandInfo in mappedCommandList) {
82                         DbCommandDefinition providerCommandDefinition = storeProviderServices.CreateCommandDefinition(providerCommandInfo.CommandTree);
83
84                         if (null == providerCommandDefinition) {
85                             throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.ProviderReturnedNullForCreateCommandDefinition);
86                         }
87                         _mappedCommandDefinitions.Add(providerCommandDefinition);
88                     }
89                 }
90                 else {
91                     Debug.Assert(DbCommandTreeKind.Function == commandTree.CommandTreeKind, "only query and function command trees are supported");
92                     DbFunctionCommandTree entityCommandTree = (DbFunctionCommandTree)commandTree;
93
94                     // Retrieve mapping and metadata information for the function import.
95                     FunctionImportMappingNonComposable mapping = GetTargetFunctionMapping(entityCommandTree);
96                     IList<FunctionParameter> returnParameters = entityCommandTree.EdmFunction.ReturnParameters;
97                     int resultSetCount = returnParameters.Count > 1 ? returnParameters.Count : 1;
98                     _columnMapGenerators = new IColumnMapGenerator[resultSetCount];
99                     TypeUsage storeResultType = DetermineStoreResultType(entityCommandTree.MetadataWorkspace, mapping, 0, out _columnMapGenerators[0]);
100                     for (int i = 1; i < resultSetCount; i++)
101                     {
102                         DetermineStoreResultType(entityCommandTree.MetadataWorkspace, mapping, i, out _columnMapGenerators[i]);
103                     }
104                     // Copy over parameters (this happens through a more indirect route in the plan compiler, but
105                     // it happens nonetheless)
106                     List<KeyValuePair<string, TypeUsage>> providerParameters = new List<KeyValuePair<string, TypeUsage>>();
107                     foreach (KeyValuePair<string, TypeUsage> parameter in entityCommandTree.Parameters)
108                     {
109                         providerParameters.Add(parameter);
110                     }
111
112                     // Construct store command tree usage.
113                     DbFunctionCommandTree providerCommandTree = new DbFunctionCommandTree(entityCommandTree.MetadataWorkspace, DataSpace.SSpace,
114                         mapping.TargetFunction, storeResultType, providerParameters);
115                                         
116                     DbCommandDefinition storeCommandDefinition = storeProviderServices.CreateCommandDefinition(providerCommandTree);
117                     _mappedCommandDefinitions = new List<DbCommandDefinition>(1) { storeCommandDefinition };
118
119                     EntitySet firstResultEntitySet = mapping.FunctionImport.EntitySets.FirstOrDefault();
120                     if (firstResultEntitySet != null)
121                     {
122                         _entitySets = new Set<EntitySet>();
123                         _entitySets.Add(mapping.FunctionImport.EntitySets.FirstOrDefault());
124                         _entitySets.MakeReadOnly();
125                     }
126                 }
127
128                 // Finally, build a list of the parameters that the resulting command should have;
129                 List<EntityParameter> parameterList = new List<EntityParameter>();
130
131                 foreach (KeyValuePair<string, TypeUsage> queryParameter in commandTree.Parameters) {
132                     EntityParameter parameter = CreateEntityParameterFromQueryParameter(queryParameter);
133                     parameterList.Add(parameter);
134                 }
135
136                 _parameters = new System.Collections.ObjectModel.ReadOnlyCollection<EntityParameter>(parameterList);
137             }
138             catch (EntityCommandCompilationException) {
139                 // No need to re-wrap EntityCommandCompilationException
140                 throw;
141             }
142             catch (Exception e) {
143                 // we should not be wrapping all exceptions
144                 if (EntityUtil.IsCatchableExceptionType(e)) {
145                     // we don't wan't folks to have to know all the various types of exceptions that can 
146                     // occur, so we just rethrow a CommandDefinitionException and make whatever we caught  
147                     // the inner exception of it.
148                     throw EntityUtil.CommandCompilation(System.Data.Entity.Strings.EntityClient_CommandDefinitionPreparationFailed, e);
149                 }
150                 throw;
151             }
152         }
153
154         /// <summary>
155         /// Determines the store type for a function import.
156         /// </summary>
157         private TypeUsage DetermineStoreResultType(MetadataWorkspace workspace, FunctionImportMappingNonComposable mapping, int resultSetIndex, out IColumnMapGenerator columnMapGenerator) {
158             // Determine column maps and infer result types for the mapped function. There are four varieties:
159             // Collection(Entity)
160             // Collection(PrimitiveType)
161             // Collection(ComplexType)
162             // No result type
163             TypeUsage storeResultType; 
164             {
165                 StructuralType baseStructuralType;
166                 EdmFunction functionImport = mapping.FunctionImport;
167
168                 // Collection(Entity) or Collection(ComplexType)
169                 if (MetadataHelper.TryGetFunctionImportReturnType<StructuralType>(functionImport, resultSetIndex, out baseStructuralType))
170                 {
171                     ValidateEdmResultType(baseStructuralType, functionImport);
172
173                     //Note: Defensive check for historic reasons, we expect functionImport.EntitySets.Count > resultSetIndex 
174                     EntitySet entitySet = functionImport.EntitySets.Count > resultSetIndex ? functionImport.EntitySets[resultSetIndex] : null;
175
176                     columnMapGenerator = new FunctionColumnMapGenerator(mapping, resultSetIndex, entitySet, baseStructuralType);
177
178                     // We don't actually know the return type for the stored procedure, but we can infer
179                     // one based on the mapping (i.e.: a column for every property of the mapped types
180                     // and for all discriminator columns)
181                     storeResultType = mapping.GetExpectedTargetResultType(workspace, resultSetIndex);
182                 }
183
184                 // Collection(PrimitiveType)
185                 else
186                 {
187                     FunctionParameter returnParameter = MetadataHelper.GetReturnParameter(functionImport, resultSetIndex);
188                     if (returnParameter != null && returnParameter.TypeUsage != null)
189                     {
190                         // Get metadata description of the return type 
191                         storeResultType = returnParameter.TypeUsage;
192                         Debug.Assert(storeResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType, "FunctionImport currently supports only collection result type");
193                         TypeUsage elementType = ((CollectionType)storeResultType.EdmType).TypeUsage;
194                         Debug.Assert(Helper.IsScalarType(elementType.EdmType) 
195                             , "FunctionImport supports only Collection(Entity), Collection(Enum) and Collection(Primitive)");
196
197                         // Build collection column map where the first column of the store result is assumed
198                         // to contain the primitive type values.
199                         ScalarColumnMap scalarColumnMap = new ScalarColumnMap(elementType, string.Empty, 0, 0);
200                         SimpleCollectionColumnMap collectionColumnMap = new SimpleCollectionColumnMap(storeResultType,
201                             string.Empty, scalarColumnMap, null, null);
202                         columnMapGenerator = new ConstantColumnMapGenerator(collectionColumnMap, 1);
203                     }
204
205                     // No result type
206                     else
207                     {
208                         storeResultType = null;
209                         columnMapGenerator = new ConstantColumnMapGenerator(null, 0);
210                     }
211                 }
212             }
213             return storeResultType;
214         }
215
216         /// <summary>
217         /// Handles the following negative scenarios
218         /// Nested ComplexType Property in ComplexType
219         /// </summary>
220         /// <param name="resultType"></param>
221         private void ValidateEdmResultType(EdmType resultType, EdmFunction functionImport)
222         {
223             if (Helper.IsComplexType(resultType))
224             {
225                 ComplexType complexType = resultType as ComplexType;
226                 Debug.Assert(null != complexType, "we should have a complex type here");
227
228                 foreach (var property in complexType.Properties)
229                 {
230                     if (property.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType)
231                     {
232                         throw new NotSupportedException(System.Data.Entity.Strings.ComplexTypeAsReturnTypeAndNestedComplexProperty(property.Name, complexType.Name, functionImport.FullName));
233                     }
234                 }
235             }
236         }
237
238         /// <summary>
239         /// Retrieves mapping for the given C-Space functionCommandTree
240         /// </summary>
241         private static FunctionImportMappingNonComposable GetTargetFunctionMapping(DbFunctionCommandTree functionCommandTree)
242         {
243             Debug.Assert(functionCommandTree.DataSpace == DataSpace.CSpace, "map from CSpace->SSpace function");
244             Debug.Assert(functionCommandTree != null, "null functionCommandTree");
245             Debug.Assert(!functionCommandTree.EdmFunction.IsComposableAttribute, "functionCommandTree.EdmFunction must be non-composable.");
246
247             // Find mapped store function.
248             FunctionImportMapping targetFunctionMapping;
249             if (!functionCommandTree.MetadataWorkspace.TryGetFunctionImportMapping(functionCommandTree.EdmFunction, out targetFunctionMapping))
250             {
251                 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_UnmappedFunctionImport(functionCommandTree.EdmFunction.FullName));
252             }
253             return (FunctionImportMappingNonComposable)targetFunctionMapping;
254         }
255
256         #endregion
257
258         #region public API
259         /// <summary>
260         /// Create a DbCommand object from the definition, that can be executed
261         /// </summary>
262         /// <returns></returns>
263         public override DbCommand CreateCommand() {
264             return new EntityCommand(this);
265         }
266
267         #endregion
268
269         #region internal methods
270
271         /// <summary>
272         /// Get a list of commands to be executed by the provider
273         /// </summary>
274         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
275         internal IEnumerable<string> MappedCommands {
276             get {
277                 // Build up the list of command texts, if we haven't done so yet
278                 List<string> mappedCommandTexts = new List<string>();
279                 foreach (DbCommandDefinition commandDefinition in _mappedCommandDefinitions) {
280                     DbCommand mappedCommand = commandDefinition.CreateCommand();
281                     mappedCommandTexts.Add(mappedCommand.CommandText);
282                 }
283                 return mappedCommandTexts;
284             }
285         }
286
287         /// <summary>
288         /// Creates ColumnMap for result assembly using the given reader.
289         /// </summary>
290         internal ColumnMap CreateColumnMap(DbDataReader storeDataReader) 
291         {
292             return CreateColumnMap(storeDataReader, 0);
293         }
294
295         /// <summary>
296         /// Creates ColumnMap for result assembly using the given reader's resultSetIndexth result set.
297         /// </summary>
298         internal ColumnMap CreateColumnMap(DbDataReader storeDataReader, int resultSetIndex)
299         {
300             return _columnMapGenerators[resultSetIndex].CreateColumnMap(storeDataReader);
301         }
302
303         /// <summary>
304         /// Property to expose the known parameters for the query, so the Command objects 
305         /// constructor can poplulate it's parameter collection from.
306         /// </summary>
307         internal IEnumerable<EntityParameter> Parameters {
308             get {
309                 return _parameters;
310             }
311         }
312
313         /// <summary>
314         /// Set of entity sets exposed in the command.
315         /// </summary>
316         internal Set<EntitySet> EntitySets {
317             get { 
318                 return _entitySets; 
319             }
320         }
321
322         /// <summary>
323         /// Constructs a EntityParameter from a CQT parameter.
324         /// </summary>
325         /// <param name="queryParameter"></param>
326         /// <returns></returns>
327         private static EntityParameter CreateEntityParameterFromQueryParameter(KeyValuePair<string, TypeUsage> queryParameter) {
328             // We really can't have a parameter here that isn't a scalar type...
329             Debug.Assert(TypeSemantics.IsScalarType(queryParameter.Value), "Non-scalar type used as query parameter type");
330
331             EntityParameter result = new EntityParameter();
332             result.ParameterName = queryParameter.Key;
333
334             EntityCommandDefinition.PopulateParameterFromTypeUsage(result, queryParameter.Value, isOutParam: false);
335
336             return result;
337         }
338
339         internal static void PopulateParameterFromTypeUsage(EntityParameter parameter, TypeUsage type, bool isOutParam)
340         {
341             // type can be null here if the type provided by the user is not a known model type
342             if (type != null)
343             {
344                 PrimitiveTypeKind primitiveTypeKind;
345
346                 if (Helper.IsEnumType(type.EdmType))
347                 {
348                     type = TypeUsage.Create(Helper.GetUnderlyingEdmTypeForEnumType(type.EdmType));
349                 }
350                 else if (Helper.IsSpatialType(type, out primitiveTypeKind))
351                 {
352                     parameter.EdmType = EdmProviderManifest.Instance.GetPrimitiveType(primitiveTypeKind);
353                 }
354             }
355             
356             DbCommandDefinition.PopulateParameterFromTypeUsage(parameter, type, isOutParam);            
357         }
358
359         /// <summary>
360         /// Internal execute method -- copies command information from the map command 
361         /// to the command objects, executes them, and builds the result assembly 
362         /// structures needed to return the data reader
363         /// </summary>
364         /// <param name="entityCommand"></param>
365         /// <param name="behavior"></param>
366         /// <returns></returns>
367         /// <exception cref="InvalidOperationException">behavior must specify CommandBehavior.SequentialAccess</exception>
368         /// <exception cref="InvalidOperationException">input parameters in the entityCommand.Parameters collection must have non-null values.</exception>
369         internal DbDataReader Execute(EntityCommand entityCommand, CommandBehavior behavior) {
370             if (CommandBehavior.SequentialAccess != (behavior & CommandBehavior.SequentialAccess)) {
371                 throw EntityUtil.MustUseSequentialAccess();
372             }
373
374             DbDataReader storeDataReader = ExecuteStoreCommands(entityCommand, behavior);
375             DbDataReader result = null;
376
377             // If we actually executed something, then go ahead and construct a bridge
378             // data reader for it.
379             if (null != storeDataReader) {
380                 try {
381                     ColumnMap columnMap = this.CreateColumnMap(storeDataReader, 0);
382                     if (null == columnMap) {
383                         // For a query with no result type (and therefore no column map), consume the reader.
384                         // When the user requests Metadata for this reader, we return nothing.
385                         CommandHelper.ConsumeReader(storeDataReader);
386                         result = storeDataReader;
387                     }
388                     else {
389                         result = BridgeDataReader.Create(storeDataReader, columnMap, entityCommand.Connection.GetMetadataWorkspace(), GetNextResultColumnMaps(storeDataReader));
390                     }
391                 }
392                 catch {
393                     // dispose of store reader if there is an error creating the BridgeDataReader
394                     storeDataReader.Dispose();
395                     throw;
396                 }
397             }
398             return result;
399         }
400
401         private IEnumerable<ColumnMap> GetNextResultColumnMaps(DbDataReader storeDataReader)
402         {
403             for (int i = 1; i < _columnMapGenerators.Length; ++i)
404             {
405                 yield return this.CreateColumnMap(storeDataReader, i);
406             }
407         }
408
409         /// <summary>
410         /// Execute the store commands, and return IteratorSources for each one
411         /// </summary>
412         /// <param name="entityCommand"></param>
413         /// <param name="behavior"></param>
414         internal DbDataReader ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
415         {
416             // SQLPT #120007433 is the work item to implement MARS support, which we
417             //                  need to do here, but since the PlanCompiler doesn't 
418             //                  have it yet, neither do we...
419             if (1 != _mappedCommandDefinitions.Count) {
420                 throw EntityUtil.NotSupported("MARS");
421             }
422
423             EntityTransaction entityTransaction = CommandHelper.GetEntityTransaction(entityCommand);
424
425             DbCommandDefinition definition = _mappedCommandDefinitions[0];
426             DbCommand storeProviderCommand = definition.CreateCommand();
427
428             CommandHelper.SetStoreProviderCommandState(entityCommand, entityTransaction, storeProviderCommand);
429                         
430             // Copy over the values from the map command to the store command; we 
431             // assume that they were not renamed by either the plan compiler or SQL 
432             // Generation.
433             //
434             // Note that this pretty much presumes that named parameters are supported
435             // by the store provider, but it might work if we don't reorder/reuse
436             // parameters.
437             //
438             // Note also that the store provider may choose to add parameters to thier
439             // command object for some things; we'll only copy over the values for
440             // parameters that we find in the EntityCommands parameters collection, so 
441             // we won't damage anything the store provider did.
442
443             bool hasOutputParameters = false;
444             if (storeProviderCommand.Parameters != null)    // SQLBUDT 519066
445             {
446                 DbProviderServices storeProviderServices = DbProviderServices.GetProviderServices(entityCommand.Connection.StoreProviderFactory);
447
448                 foreach (DbParameter storeParameter in storeProviderCommand.Parameters) {
449                     // I could just use the string indexer, but then if I didn't find it the
450                     // consumer would get some ParameterNotFound exeception message and that
451                     // wouldn't be very meaningful.  Instead, I use the IndexOf method and
452                     // if I don't find it, it's not a big deal (The store provider must
453                     // have added it).
454                     int parameterOrdinal = entityCommand.Parameters.IndexOf(storeParameter.ParameterName);
455                     if (-1 != parameterOrdinal) {
456                         EntityParameter entityParameter = entityCommand.Parameters[parameterOrdinal];
457
458                         SyncParameterProperties(entityParameter, storeParameter, storeProviderServices);
459
460                         if (storeParameter.Direction != ParameterDirection.Input) {
461                             hasOutputParameters = true;
462                         }
463                     }
464                 }
465             }
466
467             // If the EntityCommand has output parameters, we must synchronize parameter values when
468             // the reader is closed. Tell the EntityCommand about the store command so that it knows
469             // where to pull those values from.
470             if (hasOutputParameters) {
471                 entityCommand.SetStoreProviderCommand(storeProviderCommand);
472             }
473
474             DbDataReader reader = null;
475             try {
476                 reader = storeProviderCommand.ExecuteReader(behavior & ~CommandBehavior.SequentialAccess);
477             }
478             catch (Exception e) {
479                 // we should not be wrapping all exceptions
480                 if (EntityUtil.IsCatchableExceptionType(e)) {
481                     // we don't wan't folks to have to know all the various types of exceptions that can 
482                     // occur, so we just rethrow a CommandDefinitionException and make whatever we caught  
483                     // the inner exception of it.
484                     throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_CommandDefinitionExecutionFailed, e);
485                 }
486                 throw;
487             }
488             return reader;
489         }
490
491         /// <summary>
492         /// Updates storeParameter size, precision and scale properties from user provided parameter properties.
493         /// </summary>
494         /// <param name="entityParameter"></param>
495         /// <param name="storeParameter"></param>
496         private static void SyncParameterProperties(EntityParameter entityParameter, DbParameter storeParameter, DbProviderServices storeProviderServices) {
497             IDbDataParameter dbDataParameter = (IDbDataParameter)storeParameter;
498
499             // DBType is not currently syncable; it's part of the cache key anyway; this is because we can't guarantee
500             // that the store provider will honor it -- (SqlClient doesn't...)
501             //if (entityParameter.IsDbTypeSpecified)
502             //{
503             //    storeParameter.DbType = entityParameter.DbType;
504             //}
505
506             // Give the store provider the opportunity to set the value before any parameter state has been copied from
507             // the EntityParameter.
508             TypeUsage parameterTypeUsage = TypeHelpers.GetPrimitiveTypeUsageForScalar(entityParameter.GetTypeUsage());
509             storeProviderServices.SetParameterValue(storeParameter, parameterTypeUsage, entityParameter.Value);
510
511             // Override the store provider parameter state with any explicitly specified values from the EntityParameter.
512             if (entityParameter.IsDirectionSpecified)
513             {
514                 storeParameter.Direction = entityParameter.Direction;
515             }
516             if (entityParameter.IsIsNullableSpecified)
517             {
518                 storeParameter.IsNullable = entityParameter.IsNullable;
519             }
520             if (entityParameter.IsSizeSpecified)
521             {
522                 storeParameter.Size = entityParameter.Size;
523             }
524             if (entityParameter.IsPrecisionSpecified)
525             {
526                 dbDataParameter.Precision = entityParameter.Precision;
527             }
528             if (entityParameter.IsScaleSpecified)
529             {
530                 dbDataParameter.Scale = entityParameter.Scale;
531             }
532         }
533
534         /// <summary>
535         /// Return the string used by EntityCommand and ObjectQuery<T> ToTraceString"/>
536         /// </summary>
537         /// <returns></returns>
538         internal string ToTraceString() {
539             if (_mappedCommandDefinitions != null) {
540                 if (_mappedCommandDefinitions.Count == 1) {
541                     // Gosh it sure would be nice if I could just get the inner commandText, but
542                     // that would require more public surface area on DbCommandDefinition, or
543                     // me to know about the inner object...
544                     return _mappedCommandDefinitions[0].CreateCommand().CommandText;
545                 }
546                 else {
547                     StringBuilder sb = new StringBuilder();
548                     foreach (DbCommandDefinition commandDefinition in _mappedCommandDefinitions) {
549                         DbCommand mappedCommand = commandDefinition.CreateCommand();
550                         sb.Append(mappedCommand.CommandText);
551                     }
552                     return sb.ToString();
553                 }
554             }
555             return string.Empty;
556         }
557
558         #endregion
559
560         #region nested types
561         /// <summary>
562         /// Generates a column map given a data reader.
563         /// </summary>
564         private interface IColumnMapGenerator {
565             /// <summary>
566             /// Given a data reader, returns column map.
567             /// </summary>
568             /// <param name="reader">Data reader.</param>
569             /// <returns>Column map.</returns>
570             ColumnMap CreateColumnMap(DbDataReader reader);
571         }
572
573         /// <summary>
574         /// IColumnMapGenerator wrapping a constant instance of a column map (invariant with respect
575         /// to the given DbDataReader)
576         /// </summary>
577         private sealed class ConstantColumnMapGenerator : IColumnMapGenerator {
578             private readonly ColumnMap _columnMap;
579             private readonly int _fieldsRequired;
580
581             internal ConstantColumnMapGenerator(ColumnMap columnMap, int fieldsRequired) {
582                 _columnMap = columnMap;
583                 _fieldsRequired = fieldsRequired;
584             }
585
586             ColumnMap IColumnMapGenerator.CreateColumnMap(DbDataReader reader) {
587                 if (null != reader && reader.FieldCount < _fieldsRequired) {
588                     throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_TooFewColumns);
589                 }
590                 return _columnMap;
591             }
592         }
593
594         /// <summary>
595         /// Generates column maps for a non-composable function mapping.
596         /// </summary>
597         private sealed class FunctionColumnMapGenerator : IColumnMapGenerator {
598             private readonly FunctionImportMappingNonComposable _mapping;
599             private readonly EntitySet _entitySet;
600             private readonly StructuralType _baseStructuralType;
601             private readonly int _resultSetIndex;
602
603             internal FunctionColumnMapGenerator(FunctionImportMappingNonComposable mapping, int resultSetIndex, EntitySet entitySet, StructuralType baseStructuralType)
604             {
605                 _mapping = mapping;
606                 _entitySet = entitySet;
607                 _baseStructuralType = baseStructuralType;
608                 _resultSetIndex = resultSetIndex;
609             }
610
611             ColumnMap IColumnMapGenerator.CreateColumnMap(DbDataReader reader)
612             {
613                 return ColumnMapFactory.CreateFunctionImportStructuralTypeColumnMap(reader, _mapping, _resultSetIndex, _entitySet, _baseStructuralType);
614             }
615         }
616         #endregion
617     }
618 }