//---------------------------------------------------------------------
// <copyright file="EntityContainerEmitter.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------

using System;
using System.CodeDom;
using System.Diagnostics;
using SOM = System.Data.EntityModel.SchemaObjectModel;
using System.Collections.Generic;
using System.Data.Entity.Design;
using System.Data.Objects;
using System.Data.Entity.Design.Common;
using System.Data.Metadata.Edm;
using System.Data.Entity.Design.SsdlGenerator;
using System.Linq;
using System.Data.Common.Utils;


namespace System.Data.EntityModel.Emitters
{
    /// <summary>
    /// This class is responsible for emiting the code for the EntityContainer schema element
    /// </summary>
    internal sealed class EntityContainerEmitter : SchemaTypeEmitter
    {
        #region Fields
        string _onContextCreatedString = "OnContextCreated";
        #endregion


        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="entityContainer"></param>
        public EntityContainerEmitter(ClientApiGenerator generator, EntityContainer entityContainer)
            : base(generator, entityContainer)
        {
        }

        #endregion

        #region Properties, Methods, Events & Delegates
        /// <summary>
        /// Creates the CodeTypeDeclarations necessary to generate the code for the EntityContainer schema element
        /// </summary>
        /// <returns></returns>
        public override CodeTypeDeclarationCollection EmitApiClass()
        {
            Validate(); // emitter-specific validation

            // declare the new class
            // public partial class LOBScenario : ObjectContext
            CodeTypeDeclaration typeDecl = new CodeTypeDeclaration(Item.Name);
            typeDecl.IsPartial = true;

            // raise the TypeGenerated event
            CodeTypeReference objectContextTypeRef = TypeReference.ObjectContext;
            TypeGeneratedEventArgs eventArgs = new TypeGeneratedEventArgs(Item, objectContextTypeRef);
            Generator.RaiseTypeGeneratedEvent(eventArgs);

            if (eventArgs.BaseType != null && !eventArgs.BaseType.Equals(objectContextTypeRef))
            {
                typeDecl.BaseTypes.Add(eventArgs.BaseType);
            }
            else
            {
                typeDecl.BaseTypes.Add(TypeReference.ObjectContext);
            }
            AddInterfaces(Item.Name, typeDecl, eventArgs.AdditionalInterfaces);

            CommentEmitter.EmitSummaryComments(Item, typeDecl.Comments);
            EmitTypeAttributes(Item.Name, typeDecl, eventArgs.AdditionalAttributes);

            CreateConstructors(typeDecl);
            // adding partial OnContextCreated method 
            CreateContextPartialMethods(typeDecl);           

            foreach (EntitySetBase entitySetBase in Item.BaseEntitySets)
            {
                if (MetadataUtil.IsEntitySet(entitySetBase))
                {
                    EntitySet set = (EntitySet)entitySetBase;
                    CodeMemberProperty codeProperty = CreateEntitySetProperty(set);
                    typeDecl.Members.Add(codeProperty);

                    CodeMemberField codeField = CreateEntitySetField(set);
                    typeDecl.Members.Add(codeField);
                }
            }

            foreach (EntitySetBase entitySetBase in Item.BaseEntitySets)
            {
                if (MetadataUtil.IsEntitySet(entitySetBase))
                {
                    EntitySet set = (EntitySet)entitySetBase;
                    CodeMemberMethod codeProperty = CreateEntitySetAddObjectProperty(set);
                    typeDecl.Members.Add(codeProperty);
                }
            }

            foreach (EdmFunction functionImport in Item.FunctionImports)
            {
                if (ShouldEmitFunctionImport(functionImport))
                {
                    CodeMemberMethod functionMethod = CreateFunctionImportStructuralTypeReaderMethod(functionImport);
                    typeDecl.Members.Add(functionMethod);
                }
            }

            // additional members, if provided by the event subscriber
            AddMembers(Item.Name, typeDecl, eventArgs.AdditionalMembers);

            CodeTypeDeclarationCollection typeDecls = new CodeTypeDeclarationCollection();
            typeDecls.Add(typeDecl);
            return typeDecls;
        }

        private bool ShouldEmitFunctionImport(EdmFunction functionImport)
        {
            EdmType returnType = GetReturnTypeFromFunctionImport(functionImport);

            StructuralType structuralReturnType = returnType as StructuralType;

            // we only code gen the functionimport that has a collection of EntityType as return type and ignore the rest,
            // to be more specific, the rest include no return type and collection of scalar type.
            if (null != functionImport.EntitySet)
            {
                return true;
            }

            return false;
        }

        private EdmType GetReturnTypeFromFunctionImport(EdmFunction functionImport)
        {
            EdmType returnType = null;

            if (null != functionImport.ReturnParameter)
            {
                // determine element return type
                returnType = functionImport.ReturnParameter.TypeUsage.EdmType;

                if (Helper.IsCollectionType(returnType))
                {
                    // get the type in the collection
                    returnType = ((CollectionType)returnType).TypeUsage.EdmType;
                }
            }
            return returnType;
        }

        /// <summary>
        /// Emitter-specific validation: check if there exist entity containers and
        /// entity sets that have the same name but differ in case
        /// </summary>
        protected override void Validate()
        {
            base.Validate();
            Generator.VerifyLanguageCaseSensitiveCompatibilityForEntitySet(Item);
            VerifyEntityTypeAndSetAccessibilityCompatability();
        }

        /// <summary>
        /// Verify that Entity Set and Type have compatible accessibilty.
        /// They are compatible if the generated code will compile.
        /// </summary>
        private void VerifyEntityTypeAndSetAccessibilityCompatability()
        {
            foreach (EntitySetBase entitySetBase in Item.BaseEntitySets)
            {
                if (MetadataUtil.IsEntitySet(entitySetBase))
                {
                    EntitySet set = (EntitySet)entitySetBase;
                    if(!AreTypeAndSetAccessCompatible(GetEntityTypeAccessibility(set.ElementType), GetEntitySetPropertyAccessibility(set)))
                    {
                        Generator.AddError(
                            System.Data.Entity.Design.Strings.EntityTypeAndSetAccessibilityConflict(
                                set.ElementType.Name, GetAccessibilityCsdlStringFromMemberAttribute(GetEntityTypeAccessibility(set.ElementType)), set.Name, GetAccessibilityCsdlStringFromMemberAttribute(GetEntitySetPropertyAccessibility(set))),
                            ModelBuilderErrorCode.EntityTypeAndSetAccessibilityConflict,
                            EdmSchemaErrorSeverity.Error);
                    }
                }
            }
        }


        /// <summary>
        /// Tells whether Entity Type's specified accessibility and Entity Set Property's specified Accessibility will work together (compile) when codegen'd.
        /// False if (Type is internal and Set's Property is Public OR, type is internal and Set's property is protected).
        /// True otherwise
        /// </summary>
        private bool AreTypeAndSetAccessCompatible(MemberAttributes typeAccess, MemberAttributes setAccess)
        {
            return !(typeAccess == MemberAttributes.Assembly && (setAccess == MemberAttributes.Public || setAccess == MemberAttributes.Family));
        }


        /// <summary>
        /// Creates the necessary constructors for the entity container.
        /// </summary>
        private void CreateConstructors(CodeTypeDeclaration typeDecl)
        {
            // Empty constructor.
            //
            // public ctor()
            //    : base("name=" + ContainerName, "ContainerName")
            // {
            //      this.OnContextCreated();
            // }
            CodeConstructor emptyCtor = new CodeConstructor();
            emptyCtor.Attributes = MemberAttributes.Public;
            emptyCtor.BaseConstructorArgs.Add(new CodePrimitiveExpression("name=" + Item.Name));
            emptyCtor.BaseConstructorArgs.Add(new CodePrimitiveExpression(Item.Name));
            CommentEmitter.EmitSummaryComments(Strings.EmptyCtorSummaryComment(Item.Name, Item.Name), emptyCtor.Comments);

            emptyCtor.Statements.Add(OnContextCreatedCodeMethodInvokeExpression());
            
            typeDecl.Members.Add(emptyCtor);

            // Constructor that takes a connection string.
            //
            // public ctor(string connectionString)
            //    : base(connectionString, "ContainerName")
            // {
            //      this.OnContextCreated();
            // }
            CodeConstructor connectionStringCtor = new CodeConstructor();
            connectionStringCtor.Attributes = MemberAttributes.Public;
            CodeParameterDeclarationExpression connectionStringParam = new CodeParameterDeclarationExpression(TypeReference.String, "connectionString");
            connectionStringCtor.Parameters.Add(connectionStringParam);
            connectionStringCtor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression(connectionStringParam.Name));
            connectionStringCtor.BaseConstructorArgs.Add(new CodePrimitiveExpression(Item.Name));
            CommentEmitter.EmitSummaryComments(Strings.CtorSummaryComment(Item.Name), connectionStringCtor.Comments);

            connectionStringCtor.Statements.Add(OnContextCreatedCodeMethodInvokeExpression());

            typeDecl.Members.Add(connectionStringCtor);

            // Constructor that takes a connection
            //
            // public ctor(System.Data.EntityClient.EntityConnection connection)
            //    : base(connection, "ContainerName")
            // {
            //      this.OnContextCreated();
            // }
            CodeConstructor connectionWorkspaceCtor = new CodeConstructor();
            connectionWorkspaceCtor.Attributes = MemberAttributes.Public;
            CodeParameterDeclarationExpression connectionParam = new CodeParameterDeclarationExpression(TypeReference.AdoEntityClientType("EntityConnection"), "connection");
            connectionWorkspaceCtor.Parameters.Add(connectionParam);
            connectionWorkspaceCtor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression(connectionParam.Name));
            connectionWorkspaceCtor.BaseConstructorArgs.Add(new CodePrimitiveExpression(Item.Name));
            CommentEmitter.EmitSummaryComments(Strings.CtorSummaryComment(Item.Name), connectionWorkspaceCtor.Comments);

            connectionWorkspaceCtor.Statements.Add(OnContextCreatedCodeMethodInvokeExpression());
            typeDecl.Members.Add(connectionWorkspaceCtor);
        }

        /// <summary>
        /// Adds the OnContextCreated partial method for the entity container.
        /// </summary>
        private void CreateContextPartialMethods(CodeTypeDeclaration typeDecl)
        {
            CodeMemberMethod onContextCreatedPartialMethod = new CodeMemberMethod();
            onContextCreatedPartialMethod.Name = _onContextCreatedString;
            onContextCreatedPartialMethod.ReturnType = new CodeTypeReference(typeof(void));
            onContextCreatedPartialMethod.Attributes = MemberAttributes.Abstract | MemberAttributes.Public;
            typeDecl.Members.Add(onContextCreatedPartialMethod);

            Generator.FixUps.Add(new FixUp(Item.Name + "." + _onContextCreatedString, FixUpType.MarkAbstractMethodAsPartial));
        } 

        private CodeMemberField CreateEntitySetField(EntitySet set)
        {
            Debug.Assert(set != null, "Field is Null");

            // trying to get
            //
            // For Version < 2:
            //
            // private ObjectQuery<SpanTestsModel.Customer> _Customers = null;
            //
            // For Version >= 2:
            //
            // private ObjectSet<SpanTestsModel.Customer> _Customers = null;

            CodeMemberField codeField = new CodeMemberField();
            Generator.AttributeEmitter.EmitGeneratedCodeAttribute(codeField);
            codeField.Attributes = MemberAttributes.Final | MemberAttributes.Private;
            codeField.Name = Utils.FieldNameFromPropName(set.Name);


            CodeTypeReference genericParameter = Generator.GetLeastPossibleQualifiedTypeReference(set.ElementType);
            codeField.Type = TypeReference.AdoFrameworkGenericClass("ObjectQuery", genericParameter);

            return codeField;
        }

        private CodeMemberProperty CreateEntitySetProperty(EntitySet set)
        {
            Debug.Assert(set != null, "Property is Null");

            // trying to get
            //
            // [System.ComponentModel.Browsable(false)]
            // public ObjectQuery<Customer> Customers
            // {
            //      get
            //      {
            //          if ((this._Customers == null))
            //          {
            //              this._Customers = base.CreateQuery<Customer>("[Customers]");
            //          }
            //          return this._Customers;
            //      }
            // }
            //
            CodeMemberProperty codeProperty = new CodeMemberProperty();
            Generator.AttributeEmitter.EmitGeneratedCodeAttribute(codeProperty);
            codeProperty.Attributes = MemberAttributes.Final | GetEntitySetPropertyAccessibility(set);
            codeProperty.Name = set.Name;
            codeProperty.HasGet = true;
            codeProperty.HasSet = false;

            // Determine type to use for field/property and name of factory method on ObjectContext
            string typeName = "ObjectQuery";
            string createMethodName = "CreateQuery";
            // When the EntitySet name is used as CommandText, it should be quoted
            string createMethodArgument = "[" + set.Name + "]";

            CodeTypeReference genericParameter = Generator.GetLeastPossibleQualifiedTypeReference(set.ElementType);
            codeProperty.Type = TypeReference.AdoFrameworkGenericClass(typeName, genericParameter);
            string fieldName = Utils.FieldNameFromPropName(set.Name);

            // raise the PropertyGenerated event before proceeding further
            PropertyGeneratedEventArgs eventArgs = new PropertyGeneratedEventArgs(set, fieldName, codeProperty.Type);
            Generator.RaisePropertyGeneratedEvent(eventArgs);

            if (eventArgs.ReturnType == null || !eventArgs.ReturnType.Equals(codeProperty.Type))
            {
                throw EDesignUtil.InvalidOperation(Strings.CannotChangePropertyReturnType(set.Name, Item.Name));
            }

            List<CodeAttributeDeclaration> additionalAttributes = eventArgs.AdditionalAttributes;
            if (additionalAttributes != null && additionalAttributes.Count > 0)
            {
                try
                {
                    codeProperty.CustomAttributes.AddRange(additionalAttributes.ToArray());
                }
                catch (ArgumentNullException e)
                {
                    Generator.AddError(Strings.InvalidAttributeSuppliedForProperty(Item.Name),
                                               ModelBuilderErrorCode.InvalidAttributeSuppliedForProperty,
                                               EdmSchemaErrorSeverity.Error,
                                               e);
                }
            }

            // we need to insert user-specified code before other/existing code, including
            // the return statement
            List<CodeStatement> additionalGetStatements = eventArgs.AdditionalGetStatements;

            if (additionalGetStatements != null && additionalGetStatements.Count > 0)
            {
                try
                {
                    codeProperty.GetStatements.AddRange(additionalGetStatements.ToArray());
                }
                catch (ArgumentNullException e)
                {
                    Generator.AddError(Strings.InvalidGetStatementSuppliedForProperty(Item.Name),
                                       ModelBuilderErrorCode.InvalidGetStatementSuppliedForProperty,
                                       EdmSchemaErrorSeverity.Error,
                                       e);
                }
            }

            codeProperty.GetStatements.Add(
                new CodeConditionStatement(
                    EmitExpressionEqualsNull(new CodeFieldReferenceExpression(ThisRef, fieldName)),
                    new CodeAssignStatement(
                        new CodeFieldReferenceExpression(ThisRef, fieldName),
                        new CodeMethodInvokeExpression(
                            new CodeMethodReferenceExpression(
                                new CodeBaseReferenceExpression(),
                                createMethodName,
                                new CodeTypeReference[] { genericParameter }
                            ),
                            new CodePrimitiveExpression(createMethodArgument)
                        )
                    )
                )
            );

            codeProperty.GetStatements.Add(
                new CodeMethodReturnStatement(
                    new CodeFieldReferenceExpression(
                        ThisRef,
                        fieldName
                    )
                )
            );

            // property summary
            CommentEmitter.EmitSummaryComments(set, codeProperty.Comments);

            return codeProperty;
        }
        /// <summary>
        /// Create an AddTo-EntitysetName methiod for each entityset in the context.
        /// </summary>
        /// <param name="set">EntityContainerEntitySet that we will go over to get the existing entitysets.</param>
        /// <returns> Method definition </returns>

        private CodeMemberMethod CreateEntitySetAddObjectProperty(EntitySet set)
        {
            Debug.Assert(set != null, "Property is Null");

            // trying to get
            //
            // public void AddToCustomer(Customer customer)
            // {
            //      base.AddObject("Customer", customer);
            // }
            CodeMemberMethod codeMethod = new CodeMemberMethod();
            Generator.AttributeEmitter.EmitGeneratedCodeAttribute(codeMethod);
            codeMethod.Attributes = MemberAttributes.Final | GetEntityTypeAccessibility(set.ElementType);
            codeMethod.Name = ("AddTo" + set.Name);

            CodeParameterDeclarationExpression parameter = new CodeParameterDeclarationExpression();

            parameter.Type = Generator.GetLeastPossibleQualifiedTypeReference(set.ElementType);
            parameter.Name = Utils.FixParameterName(set.ElementType.Name);
            codeMethod.Parameters.Add(parameter);

            codeMethod.ReturnType = new CodeTypeReference(typeof(void));

            codeMethod.Statements.Add(
                new CodeMethodInvokeExpression(
                    new CodeBaseReferenceExpression(),
                    "AddObject",
                    new CodePrimitiveExpression(set.Name),
                    new CodeFieldReferenceExpression(null, parameter.Name)
                )
            );

            // method summary
            CommentEmitter.EmitSummaryComments(set, codeMethod.Comments);
            return codeMethod;
        }

        /// <summary>
        /// Create a method entry point for a function import yielding an entity reader.
        /// </summary>
        /// <param name="functionImport">SOM for function import; must not be null and must yield
        /// an entity reader.</param>
        /// <returns>Method definition.</returns>
        private CodeMemberMethod CreateFunctionImportStructuralTypeReaderMethod(EdmFunction functionImport)
        {
            // Trying to get:
            //
            ///// <summary>
            ///// Documentation
            ///// </summary>
            //public ObjectQueryResult<MyType> MyFunctionImport(Nullable<int> id, string foo)
            //{
            //    ObjectParameter idParameter;
            //    if (id.HasValue)
            //    {
            //        idParameter = new ObjectParameter("id", id);
            //    }
            //    else
            //    {
            //        idParameter = new ObjectParameter("id", typeof(int));
            //    }
            //    ObjectParameter fooParameter;
            //    if (null != foo)
            //    {
            //        fooParameter = new ObjectParameter("foo", foo);
            //    }
            //    else
            //    {
            //        fooParameter = new ObjectParameter("foo", typeof(string));
            //    }
            //    return base.ExecuteFunction<MyType>("MyFunctionImport", idParameter, fooParameter);
            //}
            Debug.Assert(null != functionImport);

            CodeMemberMethod method = new CodeMemberMethod();
            Generator.AttributeEmitter.EmitGeneratedCodeAttribute(method);
            method.Name = functionImport.Name;
            method.Attributes = GetFunctionImportAccessibility(functionImport) | MemberAttributes.Final;

            UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService(
                this.Generator.IsLanguageCaseSensitive, 
                s => Utils.FixParameterName(s));

            // determine element return type
            EdmType returnType = GetReturnTypeFromFunctionImport(functionImport);
            if (Helper.IsCollectionType(returnType))
            {
                // get the type in the collection
                returnType = ((CollectionType)returnType).TypeUsage.EdmType;
            }
            CodeTypeReference elementType = Generator.GetLeastPossibleQualifiedTypeReference(returnType);
            method.ReturnType = TypeReference.ObjectResult(elementType);

            // generate <summary> comments based on CSDL Documentation element
            CommentEmitter.EmitSummaryComments(functionImport, method.Comments);

            // build up list of arguments to ExecuteFunction
            List<CodeExpression> executeArguments = new List<CodeExpression>();
            executeArguments.Add(new CodePrimitiveExpression(functionImport.Name)); // first argument is the name of the function
            foreach (FunctionParameter parameter in functionImport.Parameters)
            {
                CreateFunctionArgument(method, uniqueIdentifierService, parameter);
            }

            // add fields representing object parameters
            foreach (FunctionParameter parameter in functionImport.Parameters)
            {
                if (parameter.Mode == ParameterMode.In)
                {
                    CodeExpression variableReference = CreateFunctionParameter(method, uniqueIdentifierService, parameter);
                    executeArguments.Add(variableReference);
                }
                else
                {
                    // the parameter is already being passed in as an argument; just remember it and 
                    // pass it in as an argument
                    string adjustedParameterName;
                    if (!uniqueIdentifierService.TryGetAdjustedName(parameter, out adjustedParameterName))
                    {
                        Debug.Fail("parameter must be registered in identifier service");
                    }
                    executeArguments.Add(new CodeVariableReferenceExpression(adjustedParameterName));
                }
            }

            // Add call to ExecuteFunction
            //      return ExecuteFunction<elementType>("FunctionImportName", { object parameters });
            CodeMethodReferenceExpression executeFunctionMethod = new CodeMethodReferenceExpression(
                new CodeBaseReferenceExpression(),
                "ExecuteFunction",
                new CodeTypeReference[] { elementType });
            method.Statements.Add(
                new CodeMethodReturnStatement(
                    new CodeMethodInvokeExpression(executeFunctionMethod, executeArguments.ToArray())
                )
            );

            // invoke the ExecuteFunction method passing in parameters
            return method;
        }

        private CodeExpression CreateFunctionParameter(CodeMemberMethod method, UniqueIdentifierService uniqueIdentifierService, FunctionParameter parameter)
        {
            // get (adjusted) name of parameter
            string adjustedParameterName;
            if (!uniqueIdentifierService.TryGetAdjustedName(parameter, out adjustedParameterName))
            {
                Debug.Fail("parameter must be registered in identifier service");
            }
            Type parameterType = DetermineParameterType(parameter);

            // make sure the variable name does not collide with any parameters to the method, or any
            // existing variables (all registered in the service)
            string variableName = uniqueIdentifierService.AdjustIdentifier(parameter.Name + "Parameter");

            // ObjectParameter variableName;
            // if (null != parameterName)
            // {
            //     variableName = new ObjectParameter("parameterName", adjustedParameterName);
            // }
            // else
            // {
            //     variableName = new ObjectParameter("parameterName", typeof(parameterType));
            // }
            method.Statements.Add(
                new CodeVariableDeclarationStatement(TypeReference.ForType(typeof(ObjectParameter)), variableName));
            CodeExpression variableReference = new CodeVariableReferenceExpression(variableName);
            CodeExpression parameterReference = new CodeVariableReferenceExpression(adjustedParameterName);
            CodeStatement nullConstructor = new CodeAssignStatement(variableReference,
                new CodeObjectCreateExpression(TypeReference.ForType(typeof(ObjectParameter)),
                new CodePrimitiveExpression(parameter.Name),
                new CodeTypeOfExpression(TypeReference.ForType(parameterType))));
            CodeStatement valueConstructor = new CodeAssignStatement(variableReference,
                new CodeObjectCreateExpression(TypeReference.ForType(typeof(ObjectParameter)),
                new CodePrimitiveExpression(parameter.Name),
                parameterReference));
            CodeExpression notNullCondition;
            if (parameterType.IsValueType)
            {
                // Value type parameters generate Nullable<ValueType> arguments (see CreateFunctionArgument).
                // We call Nullable<ValueType>.HasValue to determine whether the argument passed in is null
                // (since null != nullableTypeInstance does not work in VB)
                //
                // parameterReference.HasValue
                notNullCondition = new CodePropertyReferenceExpression(
                    parameterReference,
                    "HasValue");
            }
            else
            {
                // use parameterReference != null
                notNullCondition = new CodeBinaryOperatorExpression(
                    parameterReference,
                    CodeBinaryOperatorType.IdentityInequality,
                    NullExpression);
            }
            method.Statements.Add(
                new CodeConditionStatement(
                    notNullCondition,
                    new CodeStatement[] { valueConstructor, },
                    new CodeStatement[] { nullConstructor, }
                )
            );
            return variableReference;
        }

        private void CreateFunctionArgument(CodeMemberMethod method, UniqueIdentifierService uniqueIdentifierService, FunctionParameter parameter)
        {
            // get type of parameter
            Type clrType = DetermineParameterType(parameter);

            // parameters to stored procedures must be nullable
            CodeTypeReference argumentType = clrType.IsValueType ? TypeReference.NullableForType(clrType) : TypeReference.ForType(clrType);
            string parameterName = uniqueIdentifierService.AdjustIdentifier(parameter.Name, parameter);
            CodeParameterDeclarationExpression codeParameter = new CodeParameterDeclarationExpression(argumentType, parameterName);
            method.Parameters.Add(codeParameter);
        }

        // requires: parameter type is constrained to be a scalar type
        // Determines CLR type for function parameter
        private static Type DetermineParameterType(FunctionParameter parameter)
        {
            Debug.Assert(null != parameter && MetadataUtil.IsPrimitiveType(parameter.TypeUsage.EdmType),
                "validation must ensure only scalar type parameter are given");

            if (parameter.Mode != ParameterMode.In)
            {
                // non input parameter must be treated as ObjectParameter instances so that the 
                // value can be set asynchronously (after the method has yielded and the reader
                // has been consumed)
                return typeof(ObjectParameter);
            }

            PrimitiveType parameterType = (PrimitiveType)parameter.TypeUsage.EdmType;
            Type clrType = parameterType.ClrType;
            return clrType;
        }

        /// <summary>
        /// return a code expression for invoking OnContextCreated partial method
        /// </summary>
        private CodeMethodInvokeExpression OnContextCreatedCodeMethodInvokeExpression()
        {
            return (new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), _onContextCreatedString, new CodeExpression[] { }));
        }

        /// <summary>
        /// Returns the type specific SchemaElement
        /// </summary>
        private new EntityContainer Item
        {
            get
            {
                return base.Item as EntityContainer;
            }
        }

        #endregion
    }
}
