1 //------------------------------------------------------------------------------
2 // <copyright file="EntityCommandDefinition.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //------------------------------------------------------------------------------
10 namespace System.Data.EntityClient {
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;
26 /// An aggregate Command Definition used by the EntityClient layers. This is an aggregator
27 /// object that represent information from multiple underlying provider commands.
29 sealed internal class EntityCommandDefinition : DbCommandDefinition {
31 #region internal state
34 /// nested store command definitions
36 private readonly List<DbCommandDefinition> _mappedCommandDefinitions;
39 /// generates column map for the store result reader
41 private readonly IColumnMapGenerator[] _columnMapGenerators;
44 /// list of the parameters that the resulting command should have
46 private readonly System.Collections.ObjectModel.ReadOnlyCollection<EntityParameter> _parameters;
49 /// Set of entity sets exposed in the command.
51 private readonly Set<EntitySet> _entitySets;
57 /// don't let this be constructed publicly;
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");
65 DbProviderServices storeProviderServices = DbProviderServices.GetProviderServices(storeProviderFactory);
68 if (DbCommandTreeKind.Query == commandTree.CommandTreeKind) {
69 // Next compile the plan for the command tree
70 List<ProviderCommandInfo> mappedCommandList = new List<ProviderCommandInfo>();
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.
78 // Then, generate the store commands from the resulting command tree(s)
79 _mappedCommandDefinitions = new List<DbCommandDefinition>(mappedCommandList.Count);
81 foreach (ProviderCommandInfo providerCommandInfo in mappedCommandList) {
82 DbCommandDefinition providerCommandDefinition = storeProviderServices.CreateCommandDefinition(providerCommandInfo.CommandTree);
84 if (null == providerCommandDefinition) {
85 throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.ProviderReturnedNullForCreateCommandDefinition);
87 _mappedCommandDefinitions.Add(providerCommandDefinition);
91 Debug.Assert(DbCommandTreeKind.Function == commandTree.CommandTreeKind, "only query and function command trees are supported");
92 DbFunctionCommandTree entityCommandTree = (DbFunctionCommandTree)commandTree;
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++)
102 DetermineStoreResultType(entityCommandTree.MetadataWorkspace, mapping, i, out _columnMapGenerators[i]);
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)
109 providerParameters.Add(parameter);
112 // Construct store command tree usage.
113 DbFunctionCommandTree providerCommandTree = new DbFunctionCommandTree(entityCommandTree.MetadataWorkspace, DataSpace.SSpace,
114 mapping.TargetFunction, storeResultType, providerParameters);
116 DbCommandDefinition storeCommandDefinition = storeProviderServices.CreateCommandDefinition(providerCommandTree);
117 _mappedCommandDefinitions = new List<DbCommandDefinition>(1) { storeCommandDefinition };
119 EntitySet firstResultEntitySet = mapping.FunctionImport.EntitySets.FirstOrDefault();
120 if (firstResultEntitySet != null)
122 _entitySets = new Set<EntitySet>();
123 _entitySets.Add(mapping.FunctionImport.EntitySets.FirstOrDefault());
124 _entitySets.MakeReadOnly();
128 // Finally, build a list of the parameters that the resulting command should have;
129 List<EntityParameter> parameterList = new List<EntityParameter>();
131 foreach (KeyValuePair<string, TypeUsage> queryParameter in commandTree.Parameters) {
132 EntityParameter parameter = CreateEntityParameterFromQueryParameter(queryParameter);
133 parameterList.Add(parameter);
136 _parameters = new System.Collections.ObjectModel.ReadOnlyCollection<EntityParameter>(parameterList);
138 catch (EntityCommandCompilationException) {
139 // No need to re-wrap EntityCommandCompilationException
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);
155 /// Determines the store type for a function import.
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)
163 TypeUsage storeResultType;
165 StructuralType baseStructuralType;
166 EdmFunction functionImport = mapping.FunctionImport;
168 // Collection(Entity) or Collection(ComplexType)
169 if (MetadataHelper.TryGetFunctionImportReturnType<StructuralType>(functionImport, resultSetIndex, out baseStructuralType))
171 ValidateEdmResultType(baseStructuralType, functionImport);
173 //Note: Defensive check for historic reasons, we expect functionImport.EntitySets.Count > resultSetIndex
174 EntitySet entitySet = functionImport.EntitySets.Count > resultSetIndex ? functionImport.EntitySets[resultSetIndex] : null;
176 columnMapGenerator = new FunctionColumnMapGenerator(mapping, resultSetIndex, entitySet, baseStructuralType);
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);
184 // Collection(PrimitiveType)
187 FunctionParameter returnParameter = MetadataHelper.GetReturnParameter(functionImport, resultSetIndex);
188 if (returnParameter != null && returnParameter.TypeUsage != null)
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)");
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);
208 storeResultType = null;
209 columnMapGenerator = new ConstantColumnMapGenerator(null, 0);
213 return storeResultType;
217 /// Handles the following negative scenarios
218 /// Nested ComplexType Property in ComplexType
220 /// <param name="resultType"></param>
221 private void ValidateEdmResultType(EdmType resultType, EdmFunction functionImport)
223 if (Helper.IsComplexType(resultType))
225 ComplexType complexType = resultType as ComplexType;
226 Debug.Assert(null != complexType, "we should have a complex type here");
228 foreach (var property in complexType.Properties)
230 if (property.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType)
232 throw new NotSupportedException(System.Data.Entity.Strings.ComplexTypeAsReturnTypeAndNestedComplexProperty(property.Name, complexType.Name, functionImport.FullName));
239 /// Retrieves mapping for the given C-Space functionCommandTree
241 private static FunctionImportMappingNonComposable GetTargetFunctionMapping(DbFunctionCommandTree functionCommandTree)
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.");
247 // Find mapped store function.
248 FunctionImportMapping targetFunctionMapping;
249 if (!functionCommandTree.MetadataWorkspace.TryGetFunctionImportMapping(functionCommandTree.EdmFunction, out targetFunctionMapping))
251 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_UnmappedFunctionImport(functionCommandTree.EdmFunction.FullName));
253 return (FunctionImportMappingNonComposable)targetFunctionMapping;
260 /// Create a DbCommand object from the definition, that can be executed
262 /// <returns></returns>
263 public override DbCommand CreateCommand() {
264 return new EntityCommand(this);
269 #region internal methods
272 /// Get a list of commands to be executed by the provider
274 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
275 internal IEnumerable<string> MappedCommands {
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);
283 return mappedCommandTexts;
288 /// Creates ColumnMap for result assembly using the given reader.
290 internal ColumnMap CreateColumnMap(DbDataReader storeDataReader)
292 return CreateColumnMap(storeDataReader, 0);
296 /// Creates ColumnMap for result assembly using the given reader's resultSetIndexth result set.
298 internal ColumnMap CreateColumnMap(DbDataReader storeDataReader, int resultSetIndex)
300 return _columnMapGenerators[resultSetIndex].CreateColumnMap(storeDataReader);
304 /// Property to expose the known parameters for the query, so the Command objects
305 /// constructor can poplulate it's parameter collection from.
307 internal IEnumerable<EntityParameter> Parameters {
314 /// Set of entity sets exposed in the command.
316 internal Set<EntitySet> EntitySets {
323 /// Constructs a EntityParameter from a CQT parameter.
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");
331 EntityParameter result = new EntityParameter();
332 result.ParameterName = queryParameter.Key;
334 EntityCommandDefinition.PopulateParameterFromTypeUsage(result, queryParameter.Value, isOutParam: false);
339 internal static void PopulateParameterFromTypeUsage(EntityParameter parameter, TypeUsage type, bool isOutParam)
341 // type can be null here if the type provided by the user is not a known model type
344 PrimitiveTypeKind primitiveTypeKind;
346 if (Helper.IsEnumType(type.EdmType))
348 type = TypeUsage.Create(Helper.GetUnderlyingEdmTypeForEnumType(type.EdmType));
350 else if (Helper.IsSpatialType(type, out primitiveTypeKind))
352 parameter.EdmType = EdmProviderManifest.Instance.GetPrimitiveType(primitiveTypeKind);
356 DbCommandDefinition.PopulateParameterFromTypeUsage(parameter, type, isOutParam);
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
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();
374 DbDataReader storeDataReader = ExecuteStoreCommands(entityCommand, behavior);
375 DbDataReader result = null;
377 // If we actually executed something, then go ahead and construct a bridge
378 // data reader for it.
379 if (null != storeDataReader) {
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;
389 result = BridgeDataReader.Create(storeDataReader, columnMap, entityCommand.Connection.GetMetadataWorkspace(), GetNextResultColumnMaps(storeDataReader));
393 // dispose of store reader if there is an error creating the BridgeDataReader
394 storeDataReader.Dispose();
401 private IEnumerable<ColumnMap> GetNextResultColumnMaps(DbDataReader storeDataReader)
403 for (int i = 1; i < _columnMapGenerators.Length; ++i)
405 yield return this.CreateColumnMap(storeDataReader, i);
410 /// Execute the store commands, and return IteratorSources for each one
412 /// <param name="entityCommand"></param>
413 /// <param name="behavior"></param>
414 internal DbDataReader ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
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");
423 EntityTransaction entityTransaction = CommandHelper.GetEntityTransaction(entityCommand);
425 DbCommandDefinition definition = _mappedCommandDefinitions[0];
426 DbCommand storeProviderCommand = definition.CreateCommand();
428 CommandHelper.SetStoreProviderCommandState(entityCommand, entityTransaction, storeProviderCommand);
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
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
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.
443 bool hasOutputParameters = false;
444 if (storeProviderCommand.Parameters != null) // SQLBUDT 519066
446 DbProviderServices storeProviderServices = DbProviderServices.GetProviderServices(entityCommand.Connection.StoreProviderFactory);
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
454 int parameterOrdinal = entityCommand.Parameters.IndexOf(storeParameter.ParameterName);
455 if (-1 != parameterOrdinal) {
456 EntityParameter entityParameter = entityCommand.Parameters[parameterOrdinal];
458 SyncParameterProperties(entityParameter, storeParameter, storeProviderServices);
460 if (storeParameter.Direction != ParameterDirection.Input) {
461 hasOutputParameters = true;
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);
474 DbDataReader reader = null;
476 reader = storeProviderCommand.ExecuteReader(behavior & ~CommandBehavior.SequentialAccess);
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);
492 /// Updates storeParameter size, precision and scale properties from user provided parameter properties.
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;
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)
503 // storeParameter.DbType = entityParameter.DbType;
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);
511 // Override the store provider parameter state with any explicitly specified values from the EntityParameter.
512 if (entityParameter.IsDirectionSpecified)
514 storeParameter.Direction = entityParameter.Direction;
516 if (entityParameter.IsIsNullableSpecified)
518 storeParameter.IsNullable = entityParameter.IsNullable;
520 if (entityParameter.IsSizeSpecified)
522 storeParameter.Size = entityParameter.Size;
524 if (entityParameter.IsPrecisionSpecified)
526 dbDataParameter.Precision = entityParameter.Precision;
528 if (entityParameter.IsScaleSpecified)
530 dbDataParameter.Scale = entityParameter.Scale;
535 /// Return the string used by EntityCommand and ObjectQuery<T> ToTraceString"/>
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;
547 StringBuilder sb = new StringBuilder();
548 foreach (DbCommandDefinition commandDefinition in _mappedCommandDefinitions) {
549 DbCommand mappedCommand = commandDefinition.CreateCommand();
550 sb.Append(mappedCommand.CommandText);
552 return sb.ToString();
562 /// Generates a column map given a data reader.
564 private interface IColumnMapGenerator {
566 /// Given a data reader, returns column map.
568 /// <param name="reader">Data reader.</param>
569 /// <returns>Column map.</returns>
570 ColumnMap CreateColumnMap(DbDataReader reader);
574 /// IColumnMapGenerator wrapping a constant instance of a column map (invariant with respect
575 /// to the given DbDataReader)
577 private sealed class ConstantColumnMapGenerator : IColumnMapGenerator {
578 private readonly ColumnMap _columnMap;
579 private readonly int _fieldsRequired;
581 internal ConstantColumnMapGenerator(ColumnMap columnMap, int fieldsRequired) {
582 _columnMap = columnMap;
583 _fieldsRequired = fieldsRequired;
586 ColumnMap IColumnMapGenerator.CreateColumnMap(DbDataReader reader) {
587 if (null != reader && reader.FieldCount < _fieldsRequired) {
588 throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_TooFewColumns);
595 /// Generates column maps for a non-composable function mapping.
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;
603 internal FunctionColumnMapGenerator(FunctionImportMappingNonComposable mapping, int resultSetIndex, EntitySet entitySet, StructuralType baseStructuralType)
606 _entitySet = entitySet;
607 _baseStructuralType = baseStructuralType;
608 _resultSetIndex = resultSetIndex;
611 ColumnMap IColumnMapGenerator.CreateColumnMap(DbDataReader reader)
613 return ColumnMapFactory.CreateFunctionImportStructuralTypeColumnMap(reader, _mapping, _resultSetIndex, _entitySet, _baseStructuralType);