File: System\Data\Objects\Internal\ObjectQueryExecutionPlan.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="ObjectQueryExecutionPlan.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Objects.Internal
{
    using System;
    using System.Data.Common;
    using System.Data.Common.CommandTrees;
    using System.Data.Common.Internal.Materialization;
    using System.Data.Common.QueryCache;
    using System.Data.Common.Utils;
    using System.Data.EntityClient;
    using System.Data.Metadata.Edm;
    using System.Data.Objects;
    using System.Diagnostics;
    using CompiledQueryParameters = System.Collections.ObjectModel.ReadOnlyCollection<System.Collections.Generic.KeyValuePair<ObjectParameter, System.Data.Objects.ELinq.QueryParameterExpression>>;
    
    /// <summary>
    /// Represents the 'compiled' form of all elements (query + result assembly) required to execute a specific <see cref="ObjectQuery"/>
    /// </summary>
    internal sealed class ObjectQueryExecutionPlan
    {
        internal readonly DbCommandDefinition CommandDefinition;
        internal readonly ShaperFactory ResultShaperFactory;
        internal readonly TypeUsage ResultType;
        internal readonly MergeOption MergeOption;
        internal readonly CompiledQueryParameters CompiledQueryParameters;
        
        /// <summary>If the query yields entities from a single entity set, the value is stored here.</summary>
        private readonly EntitySet _singleEntitySet;
 
        private ObjectQueryExecutionPlan(DbCommandDefinition commandDefinition, ShaperFactory resultShaperFactory, TypeUsage resultType, MergeOption mergeOption, EntitySet singleEntitySet, CompiledQueryParameters compiledQueryParameters)
        {
            Debug.Assert(commandDefinition != null, "A command definition is required");
            Debug.Assert(resultShaperFactory != null, "A result shaper factory is required");
            Debug.Assert(resultType != null, "A result type is required");
 
            this.CommandDefinition = commandDefinition;
            this.ResultShaperFactory = resultShaperFactory;
            this.ResultType = resultType;
            this.MergeOption = mergeOption;
            this._singleEntitySet = singleEntitySet;
            this.CompiledQueryParameters = compiledQueryParameters;
        }
 
        internal static ObjectQueryExecutionPlan Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Span span, CompiledQueryParameters compiledQueryParameters, AliasGenerator aliasGenerator)
        {
            TypeUsage treeResultType = tree.Query.ResultType;
 
            // Rewrite this tree for Span?
            DbExpression spannedQuery = null;
            SpanIndex spanInfo;
            if (ObjectSpanRewriter.TryRewrite(tree, span, mergeOption, aliasGenerator, out spannedQuery, out spanInfo))
            {
                tree = DbQueryCommandTree.FromValidExpression(tree.MetadataWorkspace, tree.DataSpace, spannedQuery);
            }
            else
            {
                spanInfo = null;
            }
 
            DbConnection connection = context.Connection;
            DbCommandDefinition definition = null;
 
            // The connection is required to get to the CommandDefinition builder.
            if (connection == null)
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_InvalidConnection);
            }
                        
            DbProviderServices services = DbProviderServices.GetProviderServices(connection);
 
            try
            {
                definition = services.CreateCommandDefinition(tree);
            }
            catch (EntityCommandCompilationException)
            {
                // If we're running against EntityCommand, we probably already caught the providers'
                // exception and wrapped it, we don't want to do that again, so we'll just rethrow
                // here instead.
                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;
            }
 
            if (definition == null)
            {
                throw EntityUtil.ProviderDoesNotSupportCommandTrees();
            }
 
            EntityCommandDefinition entityDefinition = (EntityCommandDefinition)definition;
            QueryCacheManager cacheManager = context.Perspective.MetadataWorkspace.GetQueryCacheManager();
            
            ShaperFactory shaperFactory = ShaperFactory.Create(elementType, cacheManager, entityDefinition.CreateColumnMap(null),
                context.MetadataWorkspace, spanInfo, mergeOption, false);
 
            // attempt to determine entity information for this query (e.g. which entity type and which entity set)
            //EntityType rootEntityType = null;
 
            EntitySet singleEntitySet = null;
 
            if (treeResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
            {
                // determine if the entity set is unambiguous given the entity type
                if (null != entityDefinition.EntitySets)
                {
                    foreach (EntitySet entitySet in entityDefinition.EntitySets)
                    {
                        if (null != entitySet)
                        {
                            if (entitySet.ElementType.IsAssignableFrom(((CollectionType)treeResultType.EdmType).TypeUsage.EdmType))
                            {
                                if (singleEntitySet == null)
                                {
                                    // found a single match
                                    singleEntitySet = entitySet;
                                }
                                else
                                {
                                    // there's more than one matching entity set
                                    singleEntitySet = null;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
 
            return new ObjectQueryExecutionPlan(definition, shaperFactory, treeResultType, mergeOption, singleEntitySet, compiledQueryParameters);
        }
 
        internal string ToTraceString()
        {
            string traceString = string.Empty;
            EntityCommandDefinition entityCommandDef = this.CommandDefinition as EntityCommandDefinition;
            if (entityCommandDef != null)
            {
                traceString = entityCommandDef.ToTraceString();
            }
            return traceString;
        }
 
        internal ObjectResult<TResultType> Execute<TResultType>(ObjectContext context, ObjectParameterCollection parameterValues)
        {
            DbDataReader storeReader = null;
            try
            {
                // create entity command (just do this to snarf store command)
                EntityCommandDefinition commandDefinition = (EntityCommandDefinition)this.CommandDefinition;
                EntityCommand entityCommand = new EntityCommand((EntityConnection)context.Connection, commandDefinition);
 
                // pass through parameters and timeout values
                if (context.CommandTimeout.HasValue)
                {
                    entityCommand.CommandTimeout = context.CommandTimeout.Value;
                }
 
                if (parameterValues != null)
                {
                    foreach (ObjectParameter parameter in parameterValues)
                    {
                        int index = entityCommand.Parameters.IndexOf(parameter.Name);
 
                        if (index != -1)
                        {
                            entityCommand.Parameters[index].Value = parameter.Value ?? DBNull.Value;
                        }
                    }
                }
 
                // acquire store reader
                storeReader = commandDefinition.ExecuteStoreCommands(entityCommand, CommandBehavior.Default);
 
                ShaperFactory<TResultType> shaperFactory = (ShaperFactory<TResultType>)this.ResultShaperFactory;
                Shaper<TResultType> shaper = shaperFactory.Create(storeReader, context, context.MetadataWorkspace, this.MergeOption, true);
 
                // create materializer delegate
                TypeUsage resultItemEdmType;
 
                if (ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
                {
                    resultItemEdmType = ((CollectionType)ResultType.EdmType).TypeUsage;
                }
                else
                {
                    resultItemEdmType = ResultType;
                }
 
                return new ObjectResult<TResultType>(shaper, this._singleEntitySet, resultItemEdmType);
            }
            catch (Exception)
            {
                if (null != storeReader)
                {
                    // Note: The caller is responsible for disposing reader if creating
                    // the enumerator fails.
                    storeReader.Dispose();
                }
                throw;
            }
        }
 
        internal static ObjectResult<TResultType> ExecuteCommandTree<TResultType>(ObjectContext context, DbQueryCommandTree query, MergeOption mergeOption)
        {
            Debug.Assert(context != null, "ObjectContext cannot be null");
            Debug.Assert(query != null, "Command tree cannot be null");
 
            ObjectQueryExecutionPlan execPlan = ObjectQueryExecutionPlan.Prepare(context, query, typeof(TResultType), mergeOption, null, null, System.Data.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.AliasGenerator);
            return execPlan.Execute<TResultType>(context, null);
        }
    }
}