EntityCommandDefinition.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataEntity / System / Data / EntityClient / EntityCommandDefinition.cs / 1305376 / EntityCommandDefinition.cs

                            //------------------------------------------------------------------------------ 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner  [....]
// @backupOwner [....] 
//----------------------------------------------------------------------------- 

namespace System.Data.EntityClient { 

    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Data.Common; 
    using System.Data.Common.CommandTrees;
    using System.Data.Query.InternalTrees; 
    using System.Data.Metadata.Edm; 
    using System.Data.Query.ResultAssembly;
    using System.Data.Query.PlanCompiler; 
    using System.Diagnostics;
    using System.Data.Common.Utils;
    using System.Text;
    using System.Data.Mapping; 

    ///  
    /// An aggregate Command Definition used by the EntityClient layers.  This is an aggregator 
    /// object that represent information from multiple underlying provider commands.
    ///  
    sealed internal class EntityCommandDefinition : DbCommandDefinition {

        #region internal state
 
        /// 
        /// Bid Counter object 
        ///  
        private static int _objectTypeCount;
 
        /// 
        /// The object Id of this instance, for BID tracing.
        /// 
        internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); 

        ///  
        /// nested store command definitions 
        /// 
        private readonly List _mappedCommandDefinitions; 

        /// 
        /// generates column map for the store result reader
        ///  
        private readonly IColumnMapGenerator _columnMapGenerator;
 
        ///  
        /// list of the parameters that the resulting command should have
        ///  
        private readonly System.Collections.ObjectModel.ReadOnlyCollection _parameters;

        /// 
        /// Set of entity sets exposed in the command. 
        /// 
        private readonly Set _entitySets; 
 
        #endregion
 
        #region constructors
        /// 
        /// don't let this be constructed publicly;
        ///  
        /// Cannot prepare the command definition for execution; consult the InnerException for more information.
        /// The ADO.NET Data Provider you are using does not support CommandTrees. 
        internal EntityCommandDefinition(DbProviderFactory storeProviderFactory, DbCommandTree commandTree) { 
            EntityUtil.CheckArgumentNull(storeProviderFactory, "storeProviderFactory");
            EntityUtil.CheckArgumentNull(commandTree, "commandTree"); 

            EntityBid.Trace(" %d# Constructing. commandTree=%d#\n", ObjectID, commandTree.ObjectId);

            DbProviderServices storeProviderServices = DbProviderServices.GetProviderServices(storeProviderFactory); 

            try { 
                if (DbCommandTreeKind.Query == commandTree.CommandTreeKind) { 
                    // Next compile the plan for the command tree
                    List mappedCommandList = new List(); 
                    ColumnMap columnMap;
                    int columnCount;
                    PlanCompiler.Compile(commandTree, out mappedCommandList, out columnMap, out columnCount, out _entitySets);
                    _columnMapGenerator = new ConstantColumnMapGenerator(columnMap, columnCount); 
                    // Note: we presume that the first item in the ProviderCommandInfo is the root node;
                    Debug.Assert(mappedCommandList.Count > 0, "empty providerCommandInfo collection and no exception?"); // this shouldn't ever happen. 
 
                    // Then, generate the store commands from the resulting command tree(s)
                    _mappedCommandDefinitions = new List(mappedCommandList.Count); 

                    foreach (ProviderCommandInfo providerCommandInfo in mappedCommandList) {
                        DbCommandDefinition providerCommandDefinition = storeProviderServices.CreateCommandDefinition(providerCommandInfo.CommandTree);
 
                        if (null == providerCommandDefinition) {
                            throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.ProviderReturnedNullForCreateCommandDefinition); 
                        } 
                        _mappedCommandDefinitions.Add(providerCommandDefinition);
                    } 
                }
                else {
                    Debug.Assert(DbCommandTreeKind.Function == commandTree.CommandTreeKind, "only query and function command trees are supported");
                    DbFunctionCommandTree entityCommandTree = (DbFunctionCommandTree)commandTree; 

                    // Retrieve mapping and metadata information for the function import. 
                    FunctionImportMapping mapping = GetTargetFunctionMapping(entityCommandTree); 
                    TypeUsage storeResultType = DetermineStoreResultType(entityCommandTree.MetadataWorkspace, mapping, out _columnMapGenerator);
 
                    // Copy over parameters (this happens through a more indirect route in the plan compiler, but
                    // it happens nonetheless)
                    List> providerParameters = new List>();
                    foreach (KeyValuePair parameter in entityCommandTree.Parameters) 
                    {
                        providerParameters.Add(parameter); 
                    } 

                    // Construct store command tree usage. 
                    DbFunctionCommandTree providerCommandTree = new DbFunctionCommandTree(entityCommandTree.MetadataWorkspace, DataSpace.SSpace,
                        mapping.TargetFunction, storeResultType, providerParameters);

                    DbCommandDefinition storeCommandDefinition = storeProviderServices.CreateCommandDefinition(providerCommandTree); 
                    _mappedCommandDefinitions = new List(1) { storeCommandDefinition };
                    if (null != mapping.FunctionImport.EntitySet) { 
                        _entitySets = new Set(); 
                        _entitySets.Add(mapping.FunctionImport.EntitySet);
                        _entitySets.MakeReadOnly(); 
                    }
                }

                // Finally, build a list of the parameters that the resulting command should have; 
                List parameterList = new List();
 
                foreach (KeyValuePair queryParameter in commandTree.Parameters) { 
                    EntityParameter parameter = CreateEntityParameterFromQueryParameter(queryParameter);
                    parameterList.Add(parameter); 
                }

                _parameters = new System.Collections.ObjectModel.ReadOnlyCollection(parameterList);
            } 
            catch (EntityCommandCompilationException) {
                // No need to re-wrap EntityCommandCompilationException 
                throw; 
            }
            catch (Exception e) { 
                // we should not be wrapping all exceptions
                if (EntityUtil.IsCatchableExceptionType(e)) {
                    // we don't wan't folks to have to know all the various types of exceptions that can
                    // occur, so we just rethrow a CommandDefinitionException and make whatever we caught 
                    // the inner exception of it.
                    throw EntityUtil.CommandCompilation(System.Data.Entity.Strings.EntityClient_CommandDefinitionPreparationFailed, e); 
                } 
                throw;
            } 
        }

        /// 
        /// Determines the store type for a function import. 
        /// 
        private TypeUsage DetermineStoreResultType(MetadataWorkspace workspace, FunctionImportMapping mapping, out IColumnMapGenerator columnMapGenerator) { 
            // Determine column maps and infer result types for the mapped function. There are four varieties: 
            // Collection(Entity)
            // Collection(PrimitiveType) 
            // Collection(ComplexType)
            // No result type
            TypeUsage storeResultType;
            { 
                StructuralType baseStructuralType;
                EdmFunction functionImport = mapping.FunctionImport; 
 
                // Collection(Entity) or Collection(ComplexType)
                if (MetadataHelper.TryGetFunctionImportReturnType(functionImport, out baseStructuralType)) 
                {
                    ValidateEdmResultType(baseStructuralType, functionImport);

                    EntitySet entitySet = functionImport.EntitySet; 
                    columnMapGenerator = new FunctionColumnMapGenerator(mapping, entitySet, baseStructuralType);
 
                    // We don't actually know the return type for the stored procedure, but we can infer 
                    // one based on the mapping (i.e.: a column for every property of the mapped types
                    // and for all discriminator columns) 
                    storeResultType = mapping.GetExpectedTargetResultType(workspace);
                }

                // Collection(PrimitiveType) 
                else if (functionImport.ReturnParameter != null && functionImport.ReturnParameter.TypeUsage != null) {
                    // Get components of metadata description 
                    storeResultType = functionImport.ReturnParameter.TypeUsage; 
                    Debug.Assert(storeResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType, "FunctionImport currently supports only collection result type");
                    TypeUsage elementType = ((CollectionType)storeResultType.EdmType).TypeUsage; 
                    Debug.Assert(elementType.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType, "FunctionImport supports only Collection(Entity) and Collection(Primitive)");

                    // Build collection column map where the first column of the store result is assumed
                    // to contain the primitive type values. 
                    ScalarColumnMap scalarColumnMap = new ScalarColumnMap(elementType, string.Empty, 0, 0);
                    SimpleCollectionColumnMap collectionColumnMap = new SimpleCollectionColumnMap(storeResultType, 
                        string.Empty, scalarColumnMap, null, null); 
                    columnMapGenerator = new ConstantColumnMapGenerator(collectionColumnMap, 1);
                } 

                // No result type
                else {
                    storeResultType = null; 
                    columnMapGenerator = new ConstantColumnMapGenerator(null, 0);
                } 
            } 
            return storeResultType;
        } 

        /// 
        /// Handles the following negative scenarios
        /// Nested ComplexType Property in ComplexType 
        /// 
        ///  
        private void ValidateEdmResultType(EdmType resultType, EdmFunction functionImport) 
        {
            if (Helper.IsComplexType(resultType)) 
            {
                ComplexType complexType = resultType as ComplexType;
                Debug.Assert(null != complexType, "we should have a complex type here");
 
                foreach (var property in complexType.Properties)
                { 
                    if (property.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType) 
                    {
                        throw new NotSupportedException(System.Data.Entity.Strings.ComplexTypeAsReturnTypeAndNestedComplexProperty(property.Name, complexType.Name, functionImport.FullName)); 
                    }
                }
            }
        } 

        ///  
        /// Retrieves mapping for the given C-Space functionCommandTree 
        /// 
        private static FunctionImportMapping GetTargetFunctionMapping(DbFunctionCommandTree functionCommandTree) { 
            Debug.Assert(functionCommandTree.DataSpace == DataSpace.CSpace, "map from CSpace->SSpace function");
            Debug.Assert(null != functionCommandTree, "null functionCommandTree");

            // find mapped store function 
            FunctionImportMapping targetFunctionMapping;
            if (!functionCommandTree.MetadataWorkspace.TryGetFunctionImportMapping(functionCommandTree.EdmFunction, out targetFunctionMapping)) { 
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_UnmappedFunctionImport); 
            }
            return targetFunctionMapping; 
        }

        #endregion
 
        #region public API
        ///  
        /// Create a DbCommand object from the definition, that can be executed 
        /// 
        ///  
        public override DbCommand CreateCommand() {
            EntityBid.Trace(" %d#\n", ObjectID);
            return new EntityCommand(this);
        } 

        #endregion 
 
        #region internal methods
 
        /// 
        /// Get a list of commands to be executed by the provider
        /// 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 
        internal IEnumerable MappedCommands {
            get { 
                // Build up the list of command texts, if we haven't done so yet 
                List mappedCommandTexts = new List();
                foreach (DbCommandDefinition commandDefinition in _mappedCommandDefinitions) { 
                    DbCommand mappedCommand = commandDefinition.CreateCommand();
                    mappedCommandTexts.Add(mappedCommand.CommandText);
                }
                return mappedCommandTexts; 
            }
        } 
 
        /// 
        /// Creates ColumnMap for result assembly using the given reader. 
        /// 
        internal ColumnMap CreateColumnMap(DbDataReader storeDataReader) {
            return _columnMapGenerator.CreateColumnMap(storeDataReader);
        } 

        ///  
        /// Property to expose the known parameters for the query, so the Command objects 
        /// constructor can poplulate it's parameter collection from.
        ///  
        internal IEnumerable Parameters {
            get {
                return _parameters;
            } 
        }
 
        ///  
        /// Set of entity sets exposed in the command.
        ///  
        internal Set EntitySets {
            get {
                return _entitySets;
            } 
        }
 
        ///  
        /// Constructs a EntityParameter from a CQT parameter.
        ///  
        /// 
        /// 
        private static EntityParameter CreateEntityParameterFromQueryParameter(KeyValuePair queryParameter) {
            // We really can't have a parameter here that isn't a scalar type... 
            Debug.Assert(TypeSemantics.IsPrimitiveType(queryParameter.Value), "Non-PrimitiveType used as query parameter type");
 
            EntityParameter result = new EntityParameter(); 
            result.ParameterName = queryParameter.Key;
 
            DbCommandDefinition.PopulateParameterFromTypeUsage(result, queryParameter.Value, false /* isOutParam */ );

            return result;
        } 

        ///  
        /// Internal execute method -- copies command information from the map command 
        /// to the command objects, executes them, and builds the result assembly
        /// components needed to return the data reader 
        /// 
        /// 
        /// 
        ///  
        /// behavior must specify CommandBehavior.SequentialAccess
        /// input parameters in the entityCommand.Parameters collection must have non-null values. 
        internal DbDataReader Execute(EntityCommand entityCommand, CommandBehavior behavior) { 
            IntPtr hscp;
            EntityBid.ScopeEnter(out hscp, " %d#\n", ObjectID); 

            try {
                if (CommandBehavior.SequentialAccess != (behavior & CommandBehavior.SequentialAccess)) {
                    throw EntityUtil.MustUseSequentialAccess(); 
                }
 
                DbDataReader storeDataReader = ExecuteStoreCommands(entityCommand, behavior); 
                DbDataReader result = null;
 
                // If we actually executed something, then go ahead and construct a bridge
                // data reader for it.
                if (null != storeDataReader) {
                    try { 
                        ColumnMap columnMap = _columnMapGenerator.CreateColumnMap(storeDataReader);
                        if (null == columnMap) { 
                            // For a query with no result type (and therefore no column map), consume the reader. 
                            // When the user requests Metadata for this reader, we return nothing.
                            CommandHelper.ConsumeReader(storeDataReader); 
                            result = storeDataReader;
                        }
                        else {
                            result = BridgeDataReader.Create(storeDataReader, columnMap, entityCommand.Connection.GetMetadataWorkspace()); 
                        }
                    } 
                    catch { 
                        // dispose of store reader if there is an error creating the BridgeDataReader
                        storeDataReader.Dispose(); 
                        throw;
                    }
                }
                return result; 
            }
            finally { 
                EntityBid.ScopeLeave(ref hscp); 
            }
        } 

        /// 
        /// Execute the store commands, and return IteratorSources for each one
        ///  
        /// 
        ///  
        internal DbDataReader ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior) { 
            // SQLPT #120007433 is the work item to implement MARS support, which we
            //                  need to do here, but since the PlanCompiler doesn't 
            //                  have it yet, neither do we...
            if (1 != _mappedCommandDefinitions.Count) {
                throw EntityUtil.NotSupported("MARS");
            } 

            EntityTransaction entityTransaction = CommandHelper.GetEntityTransaction(entityCommand); 
 
            DbCommandDefinition definition = _mappedCommandDefinitions[0];
            DbCommand storeProviderCommand = definition.CreateCommand(); 

            CommandHelper.SetStoreProviderCommandState(entityCommand, entityTransaction, storeProviderCommand);

            // Copy over the values from the map command to the store command; we 
            // assume that they were not renamed by either the plan compiler or SQL
            // Generation. 
            // 
            // Note that this pretty much presumes that named parameters are supported
            // by the store provider, but it might work if we don't reorder/reuse 
            // parameters.
            //
            // Note also that the store provider may choose to add parameters to thier
            // command object for some things; we'll only copy over the values for 
            // parameters that we find in the EntityCommands parameters collection, so
            // we won't damage anything the store provider did. 
 
            bool hasOutputParameters = false;
            if (storeProviderCommand.Parameters != null)    // SQLBUDT 519066 
            {
                DbProviderServices storeProviderServices = DbProviderServices.GetProviderServices(entityCommand.Connection.StoreProviderFactory);

                foreach (DbParameter storeParameter in storeProviderCommand.Parameters) { 
                    // I could just use the string indexer, but then if I didn't find it the
                    // consumer would get some ParameterNotFound exeception message and that 
                    // wouldn't be very meaningful.  Instead, I use the IndexOf method and 
                    // if I don't find it, it's not a big deal (The store provider must
                    // have added it). 
                    int parameterOrdinal = entityCommand.Parameters.IndexOf(storeParameter.ParameterName);
                    if (-1 != parameterOrdinal) {
                        EntityParameter entityParameter = entityCommand.Parameters[parameterOrdinal];
 
                        SyncParameterProperties(entityParameter, storeParameter, storeProviderServices);
 
                        if (storeParameter.Direction != ParameterDirection.Input) { 
                            hasOutputParameters = true;
                        } 
                    }
                }
            }
 
            // If the EntityCommand has output parameters, we must synchronize parameter values when
            // the reader is closed. Tell the EntityCommand about the store command so that it knows 
            // where to pull those values from. 
            if (hasOutputParameters) {
                entityCommand.SetStoreProviderCommand(storeProviderCommand); 
            }

            DbDataReader reader = null;
            try { 
                reader = storeProviderCommand.ExecuteReader(behavior & ~CommandBehavior.SequentialAccess);
            } 
            catch (Exception e) { 
                // we should not be wrapping all exceptions
                if (EntityUtil.IsCatchableExceptionType(e)) { 
                    // we don't wan't folks to have to know all the various types of exceptions that can
                    // occur, so we just rethrow a CommandDefinitionException and make whatever we caught
                    // the inner exception of it.
                    throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_CommandDefinitionExecutionFailed, e); 
                }
                throw; 
            } 
            return reader;
        } 

        /// 
        /// Updates storeParameter size, precision and scale properties from user provided parameter properties.
        ///  
        /// 
        ///  
        private static void SyncParameterProperties(EntityParameter entityParameter, DbParameter storeParameter, DbProviderServices storeProviderServices) { 
            IDbDataParameter dbDataParameter = (IDbDataParameter)storeParameter;
 
            // DBType is not currently syncable; it's part of the cache key anyway; this is because we can't guarantee
            // that the store provider will honor it -- (SqlClient doesn't...)
            //if (entityParameter.IsDbTypeSpecified)
            //{ 
            //    storeParameter.DbType = entityParameter.DbType;
            //} 
 
            // Give the store provider the opportunity to set the value before any parameter state has been copied from
            // the EntityParameter. 
            storeProviderServices.SetParameterValue(storeParameter, entityParameter.GetTypeUsage(), entityParameter.Value);

            // Override the store provider parameter state with any explicitly specified values from the EntityParameter.
            if (entityParameter.IsDirectionSpecified) 
            {
                storeParameter.Direction = entityParameter.Direction; 
            } 
            if (entityParameter.IsIsNullableSpecified)
            { 
                storeParameter.IsNullable = entityParameter.IsNullable;
            }
            if (entityParameter.IsSizeSpecified)
            { 
                storeParameter.Size = entityParameter.Size;
            } 
            if (entityParameter.IsPrecisionSpecified) 
            {
                dbDataParameter.Precision = entityParameter.Precision; 
            }
            if (entityParameter.IsScaleSpecified)
            {
                dbDataParameter.Scale = entityParameter.Scale; 
            }
        } 
 
        /// 
        /// Return the string used by EntityCommand and ObjectQuery ToTraceString"/> 
        /// 
        /// 
        internal string ToTraceString() {
            if (_mappedCommandDefinitions != null) { 
                if (_mappedCommandDefinitions.Count == 1) {
                    // Gosh it sure would be nice if I could just get the inner commandText, but 
                    // that would require more public surface area on DbCommandDefinition, or 
                    // me to know about the inner object...
                    return _mappedCommandDefinitions[0].CreateCommand().CommandText; 
                }
                else {
                    StringBuilder sb = new StringBuilder();
                    foreach (DbCommandDefinition commandDefinition in _mappedCommandDefinitions) { 
                        DbCommand mappedCommand = commandDefinition.CreateCommand();
                        sb.Append(mappedCommand.CommandText); 
                    } 
                    return sb.ToString();
                } 
            }
            return string.Empty;
        }
 
        #endregion
 
        #region nested types 
        /// 
        /// Generates a column map given a data reader. 
        /// 
        private interface IColumnMapGenerator {
            /// 
            /// Given a data reader, returns column map. 
            /// 
            /// Data reader. 
            /// Column map. 
            ColumnMap CreateColumnMap(DbDataReader reader);
        } 

        /// 
        /// IColumnMapGenerator wrapping a constant instance of a column map (invariant with respect
        /// to the given DbDataReader) 
        /// 
        private sealed class ConstantColumnMapGenerator : IColumnMapGenerator { 
            private readonly ColumnMap _columnMap; 
            private readonly int _fieldsRequired;
 
            internal ConstantColumnMapGenerator(ColumnMap columnMap, int fieldsRequired) {
                _columnMap = columnMap;
                _fieldsRequired = fieldsRequired;
            } 

            ColumnMap IColumnMapGenerator.CreateColumnMap(DbDataReader reader) { 
                if (null != reader && reader.FieldCount < _fieldsRequired) { 
                    throw EntityUtil.CommandExecution(System.Data.Entity.Strings.EntityClient_TooFewColumns);
                } 
                return _columnMap;
            }
        }
 
        /// 
        /// Generates column maps for a function mapping. 
        ///  
        private sealed class FunctionColumnMapGenerator : IColumnMapGenerator {
            private readonly FunctionImportMapping _mapping; 
            private readonly EntitySet _entitySet;
            private readonly StructuralType _baseStructuralType;

            internal FunctionColumnMapGenerator(FunctionImportMapping mapping, EntitySet entitySet, StructuralType baseStructuralType) 
            {
                _mapping = mapping; 
                _entitySet = entitySet; 
                _baseStructuralType = baseStructuralType;
            } 

            ColumnMap IColumnMapGenerator.CreateColumnMap(DbDataReader reader)
            {
                return ColumnMapFactory.CreateFunctionImportStructuralTypeColumnMap(reader, _mapping, _entitySet, _baseStructuralType); 
            }
        } 
        #endregion 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.


                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK