Example #1
0
        private static IList <FunctionParameter> GetStoreParameters
            (this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute) => methodInfo
        .GetParameters()
        .Select((parameterInfo, index) =>
        {
            string parameterName = parameterInfo.GetCustomAttribute <ParameterAttribute>()?.Name;
            if (string.IsNullOrWhiteSpace(parameterName))
            {
                parameterName = parameterInfo.Name;
            }

            switch (functionAttribute.Type)
            {
            case FunctionType.NiladicFunction:
                throw new NotSupportedException(
                    $"Parameter of method {methodInfo.Name} is not supported.");

            case FunctionType.AggregateFunction:
                {
                    if (index == 0)
                    {
                        return(FunctionParameter.Create(
                                   parameterName,
                                   model.GetStoreParameterPrimitiveType(
                                       methodInfo, parameterInfo, functionAttribute)
                                   .GetCollectionType(),              // Must be collection type.
                                   ParameterMode.In));
                    }

                    // Aggregate function with more than more parameter is not supported by entity framework.
                    throw new NotSupportedException(
                        $"Method {methodInfo.Name} has more than one parameters and is not supported by Entity Framework.");
                }
            }

            return(FunctionParameter.Create(
                       parameterName,
                       model.GetStoreParameterPrimitiveType(methodInfo, parameterInfo, functionAttribute),
                       parameterInfo.ParameterType == typeof(ObjectParameter)
                                ? ParameterMode.InOut
                                : ParameterMode.In));
        })
        .ToArray();
        private static string GetStoreCommandText(this MethodInfo methodInfo, FunctionAttribute functionAttribute, string functionName)
        {
            if (functionAttribute.Type == FunctionType.NonComposableScalarValuedFunction)
            {
                string schema = functionAttribute.Schema;
                schema = string.IsNullOrWhiteSpace(schema) ? string.Empty : $"[{schema}].";
                IEnumerable<string> parameterNames = methodInfo
                    .GetParameters()
                    .Select(parameterInfo =>
                    {
                        ParameterAttribute parameterAttribute = parameterInfo.GetCustomAttribute<ParameterAttribute>();
                        string parameterName = parameterAttribute?.Name;
                        return string.IsNullOrWhiteSpace(parameterName) ? parameterInfo.Name : parameterName;
                    })
                    .Select(parameterName => $"@{parameterName}");
                return $"SELECT {schema}[{functionName}]({string.Join(", ", parameterNames)})";
            }

            return null;
        }
Example #3
0
        private static string GetStoreCommandText(this MethodInfo methodInfo, FunctionAttribute functionAttribute, string functionName)
        {
            if (functionAttribute.Type == FunctionType.NonComposableScalarValuedFunction)
            {
                string schema = functionAttribute.Schema;
                schema = string.IsNullOrWhiteSpace(schema) ? string.Empty : $"[{schema}].";
                IEnumerable <string> parameterNames = methodInfo
                                                      .GetParameters()
                                                      .Select(parameterInfo =>
                {
                    ParameterAttribute parameterAttribute = parameterInfo.GetCustomAttribute <ParameterAttribute>();
                    string parameterName = parameterAttribute?.Name;
                    return(string.IsNullOrWhiteSpace(parameterName) ? parameterInfo.Name : parameterName);
                })
                                                      .Select(parameterName => $"@{parameterName}");
                return($"SELECT {schema}[{functionName}]({string.Join(", ", parameterNames)})");
            }

            return(null);
        }
Example #4
0
        private static IList <EntitySet> GetModelEntitySets(this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute)
        {
            ParameterInfo returnParameterInfo = methodInfo.ReturnParameter;

            if (returnParameterInfo == null || returnParameterInfo.ParameterType == typeof(void))
            {
                throw new NotSupportedException($"The return parameter type of {methodInfo.Name} is not supported.");
            }

            if (functionAttribute.Type == FunctionType.StoredProcedure && returnParameterInfo.ParameterType != typeof(int))
            {
                // returnParameterInfo.ParameterType is ObjectResult<T>.
                Type[] returnParameterClrTypes = methodInfo.GetStoredProcedureReturnTypes().ToArray();
                if (returnParameterClrTypes.Length > 1)
                {
                    // Stored procedure has more than one result.
                    // EdmFunctionPayload.EntitySets must be provided. Otherwise, an ArgumentException will be thrown:
                    // The EntitySets parameter must not be null for functions that return multiple result sets.
                    return(returnParameterClrTypes.Select(clrType =>
                    {
                        EntitySet modelEntitySet = model
                                                   .ConceptualModel
                                                   .Container
                                                   .EntitySets
                                                   .FirstOrDefault(entitySet => entitySet.ElementType == model.GetModelEntityType(clrType, methodInfo)); // TODO: bug.
                        if (modelEntitySet == null)
                        {
                            throw new NotSupportedException(
                                $"{clrType.FullName} for method {methodInfo.Name} is not supported in conceptual model as entity set.");
                        }

                        return modelEntitySet;
                    }).ToArray());
                }
            }
            else if (functionAttribute.Type == FunctionType.TableValuedFunction)
            {
                // returnParameterInfo.ParameterType is IQueryable<T>.
                Type       returnParameterClrType    = returnParameterInfo.ParameterType.GetGenericArguments().Single();
                EntityType returnParameterEntityType = model.GetModelEntityType(returnParameterClrType, methodInfo);
                if (returnParameterEntityType != null)
                {
                    EntitySet modelEntitySet = model
                                               .ConceptualModel
                                               .Container
                                               .EntitySets
                                               .FirstOrDefault(entitySet => entitySet.ElementType == returnParameterEntityType);
                    if (modelEntitySet == null)
                    {
                        throw new NotSupportedException(
                                  $"{returnParameterInfo.ParameterType.FullName} for method {methodInfo.Name} is not supported in conceptual model as entity set.");
                    }

                    return(new EntitySet[] { modelEntitySet });
                }
            }

            // Do not return new EntitySet[0], which causes a ArgumentException:
            // The number of entity sets should match the number of return parameters.
            return(null);
        }
Example #5
0
        private static IList <FunctionParameter> GetModelReturnParameters(
            this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute)
        {
            ParameterInfo returnParameterInfo = methodInfo.ReturnParameter;

            if (returnParameterInfo == null || returnParameterInfo.ParameterType == typeof(void))
            {
                throw new NotSupportedException($"The return parameter type of {methodInfo.Name} is not supported.");
            }

            ParameterAttribute returnParameterAttribute = returnParameterInfo.GetCustomAttribute <ParameterAttribute>();

            ResultTypeAttribute[] returnTypeAttributes = methodInfo.GetCustomAttributes <ResultTypeAttribute>().ToArray();
            IEnumerable <EdmType> modelReturnParameterEdmTypes;

            if (functionAttribute.Type == FunctionType.StoredProcedure)
            {
                if (returnParameterAttribute != null)
                {
                    throw new NotSupportedException(
                              $"{nameof(ParameterAttribute)} for method {methodInfo.Name} is not supported.");
                }

                modelReturnParameterEdmTypes = methodInfo
                                               .GetStoredProcedureReturnTypes()
                                               .Select(clrType => model.GetModelStructualType(clrType, methodInfo));
            }
            else
            {
                if (returnTypeAttributes.Any())
                {
                    throw new NotSupportedException(
                              $"{nameof(ResultTypeAttribute)} for method {methodInfo.Name} is not supported.");
                }

                if (functionAttribute.Type == FunctionType.TableValuedFunction)
                {
                    // returnParameterInfo.ParameterType is IQueryable<T>.
                    Type           returnParameterClrType             = returnParameterInfo.ParameterType.GetGenericArguments().Single();
                    StructuralType modelReturnParameterStructuralType = model.GetModelStructualType(returnParameterClrType, methodInfo);
                    modelReturnParameterEdmTypes = Enumerable.Repeat(modelReturnParameterStructuralType, 1);
                }
                else
                {
                    Type returnParameterClrType          = returnParameterInfo.ParameterType;
                    Type returnParameterAttributeClrType = returnParameterAttribute?.ClrType;
                    if (returnParameterAttributeClrType != null &&
                        returnParameterAttributeClrType != returnParameterClrType)
                    {
                        throw new NotSupportedException(
                                  $"Return parameter of method {methodInfo.Name} is of {returnParameterClrType.FullName}, but its {nameof(ParameterAttribute)}.{nameof(ParameterAttribute.ClrType)} has a different type {returnParameterAttributeClrType.FullName}");
                    }

                    PrimitiveType returnParameterPrimitiveType = model.GetModelPrimitiveType(returnParameterClrType, methodInfo);
                    modelReturnParameterEdmTypes = Enumerable.Repeat(returnParameterPrimitiveType, 1);
                }
            }

            return(modelReturnParameterEdmTypes
                   .Select((edmType, index) => FunctionParameter.Create(
                               $"ReturnType{index}",
                               functionAttribute.Type == FunctionType.ModelDefinedFunction ? edmType : edmType.GetCollectionType(),
                               ParameterMode.ReturnValue))
                   .ToArray());
        }
Example #6
0
        private static IList <FunctionParameter> GetStoreReturnParameters(
            this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute)
        {
            ParameterInfo returnParameterInfo = methodInfo.ReturnParameter;

            if (returnParameterInfo == null || returnParameterInfo.ParameterType == typeof(void))
            {
                throw new NotSupportedException($"The return type of {methodInfo.Name} is not supported.");
            }

            ParameterAttribute returnParameterAttribute = returnParameterInfo.GetCustomAttribute <ParameterAttribute>();

            ResultTypeAttribute[] returnTypeAttributes = methodInfo.GetCustomAttributes <ResultTypeAttribute>().ToArray();

            if (functionAttribute.Type == FunctionType.StoredProcedure)
            {
                if (returnParameterAttribute != null)
                {
                    throw new NotSupportedException(
                              $"{nameof(ParameterAttribute)} for return value of method {methodInfo.Name} is not supported.");
                }

                return(new FunctionParameter[0]);
            }

            if (returnTypeAttributes.Any())
            {
                throw new NotSupportedException($"{nameof(ResultTypeAttribute)} for method {methodInfo.Name} is not supported.");
            }

            if (functionAttribute.Type == FunctionType.TableValuedFunction)
            {
                if (returnParameterAttribute != null)
                {
                    throw new NotSupportedException(
                              $"{nameof(ParameterAttribute)} for return value of method {methodInfo.Name} is not supported.");
                }

                /*
                 * <CollectionType>
                 * <RowType>
                 * <Property Name="PersonID" Type="int" Nullable="false" />
                 * <Property Name="FirstName" Type="nvarchar" MaxLength="50" />
                 * <Property Name="LastName" Type="nvarchar" MaxLength="50" />
                 * <Property Name="JobTitle" Type="nvarchar" MaxLength="50" />
                 * <Property Name="BusinessEntityType" Type="nvarchar" MaxLength="50" />
                 * </RowType>
                 * </CollectionType>
                 */
                // returnParameterInfo.ParameterType is IQueryable<T>.
                Type           storeReturnParameterClrType        = returnParameterInfo.ParameterType.GetGenericArguments().Single();
                StructuralType modelReturnParameterStructuralType = model.GetModelStructualType(
                    storeReturnParameterClrType, methodInfo);
                ComplexType modelReturnParameterComplexType = modelReturnParameterStructuralType as ComplexType;
                RowType     storeReturnParameterRowType;
                if (modelReturnParameterComplexType != null)
                {
                    storeReturnParameterRowType = RowType.Create(
                        modelReturnParameterComplexType.Properties.Select(property =>
                                                                          EdmProperty.Create(property.Name, model.ProviderManifest.GetStoreType(property.TypeUsage))),
                        null);
                }
                else
                {
                    EntityType modelReturnParameterEntityType = modelReturnParameterStructuralType as EntityType;
                    if (modelReturnParameterEntityType != null)
                    {
                        storeReturnParameterRowType = RowType.Create(
                            modelReturnParameterEntityType.Properties.Select(property =>
                        {
                            var typeUsage     = TypeUsage.Create(model.ProviderManifest.GetStoreType(property.TypeUsage).EdmType, property.TypeUsage.Facets);
                            var result        = EdmProperty.Create(property.Name, typeUsage);
                            var propertyNames = new[] { nameof(EdmProperty.Name), nameof(EdmProperty.TypeUsage), nameof(EdmProperty.MetadataProperties) };
                            result.SetMetadataProperties(property.MetadataProperties.Where(m => !propertyNames.Contains(m.Name)));
                            return(result);
                        }),
                            null);
                        //storeReturnParameterRowType = RowType.Create(
                        //    modelReturnParameterEntityType.Properties.Select(property => property.Clone()),
                        //    null);
                    }
                    else
                    {
                        throw new NotSupportedException($"Structural type {modelReturnParameterStructuralType.FullName} of method {methodInfo.Name} cannot be converted to {nameof(RowType)}.");
                    }
                }

                return(new FunctionParameter[]
                {
                    FunctionParameter.Create(
                        "ReturnType",
                        storeReturnParameterRowType.GetCollectionType(),     // Collection of RowType.
                        ParameterMode.ReturnValue)
                });
            }

            if (functionAttribute.Type == FunctionType.NonComposableScalarValuedFunction)
            {
                // Non-composable scalar-valued function.
                return(new FunctionParameter[0]);
            }

            // Composable scalar-valued/Aggregate/Built in/Niladic function.
            // <Function Name="ufnGetProductListPrice" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo"
            //    ReturnType ="money">
            PrimitiveType storeReturnParameterPrimitiveType = model.GetStoreParameterPrimitiveType(methodInfo, returnParameterInfo, functionAttribute);

            return(new FunctionParameter[]
            {
                FunctionParameter.Create("ReturnType", storeReturnParameterPrimitiveType, ParameterMode.ReturnValue)
            });
        }
Example #7
0
        public static void AddFunction(
            this DbModel model,
            MethodInfo methodInfo,
            FunctionAttribute functionAttribute)
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            if (methodInfo == null)
            {
                throw new ArgumentNullException(nameof(methodInfo));
            }

            if (functionAttribute == null)
            {
                throw new ArgumentNullException(nameof(functionAttribute));
            }

            if (functionAttribute.Type == FunctionType.ModelDefinedFunction)
            {
                AddModelDefinedFunction(model, methodInfo, (ModelDefinedFunctionAttribute)functionAttribute);
                return;
            }

            /*
             * <!-- SSDL content -->
             * <edmx:StorageModels>
             * <Schema Namespace="CodeFirstDatabaseSchema" Provider="System.Data.SqlClient" ProviderManifestToken="2012" Alias="Self" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
             * <Function Name="ufnGetContactInformation" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
             * <Parameter Name="PersonID" Type="int" Mode="In" />
             * <ReturnType>
             * <CollectionType>
             * <RowType>
             *  <Property Name="PersonID" Type="int" Nullable="false" />
             *  <Property Name="FirstName" Type="nvarchar" MaxLength="50" />
             *  <Property Name="LastName" Type="nvarchar" MaxLength="50" />
             *  <Property Name="JobTitle" Type="nvarchar" MaxLength="50" />
             *  <Property Name="BusinessEntityType" Type="nvarchar" MaxLength="50" />
             * </RowType>
             * </CollectionType>
             * </ReturnType>
             * </Function>
             * <Function Name="ufnGetProductListPrice" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" ReturnType="money">
             * <Parameter Name="ProductID" Type="int" Mode="In" />
             * <Parameter Name="OrderDate" Type="datetime" Mode="In" />
             * </Function>
             * <Function Name="ufnGetProductStandardCost" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
             * <Parameter Name="ProductID" Type="int" Mode="In" />
             * <Parameter Name="OrderDate" Type="datetime" Mode="In" />
             * <CommandText>
             * SELECT [dbo].[ufnGetProductListPrice](@ProductID, @OrderDate)
             * </CommandText>
             * </Function>
             * <Function Name="uspGetCategoryAndSubCategory" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
             * <Parameter Name="CategoryID" Type="int" Mode="In" />
             * </Function>
             * <Function Name="uspGetManagerEmployees" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
             * <Parameter Name="BusinessEntityID" Type="int" Mode="In" />
             * </Function>
             * <EntityContainer Name="CodeFirstDatabase">
             * </EntityContainer>
             * <Function Name="ufnGetPersons" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
             * <Parameter Name="Name" Type="nvarchar" Mode="In" />
             * <ReturnType>
             * <CollectionType>
             * <RowType>
             *  <Property Name="BusinessEntityID" Type="int" Nullable="false" />
             *  <Property Name="Title" Type="nvarchar" MaxLength="8" />
             *  <Property Name="FirstName" Type="nvarchar" MaxLength="50" Nullable="false" />
             *  <Property Name="LastName" Type="nvarchar" MaxLength="50" Nullable="false" />
             * </RowType>
             * </CollectionType>
             * </ReturnType>
             * </Function>
             * </Schema>
             * </edmx:StorageModels>
             */
            // Build above <StorageModels> imperatively.
            string functionName = functionAttribute.FunctionName;

            if (string.IsNullOrWhiteSpace(functionName))
            {
                functionName = methodInfo.Name;
            }

            //Fix (rodro75): functions could be added several times here in case of methods overloading,
            //which is necessary in some scenarios, but we should really add the metadata just once or EF
            //would complain when compiling the model.
            //Not shure about "Ordinal" equality though.. shouldn't it be OrdinalIgnoreCase instead?
            //As far as I know function names are not case-sensitive in SQL Server.
            if (model.StoreModel.Functions.Any(x => x.Name.EqualsOrdinal(functionName)))
            {
                return;
            }

            EdmFunction storeFunction = EdmFunction.Create(
                functionName,
                FunctionAttribute.CodeFirstDatabaseSchema, // model.StoreModel.Container.Name is always "CodeFirstDatabaseSchema".
                DataSpace.SSpace,                          // <edmx:StorageModels>
                new EdmFunctionPayload()
            {
                Schema                 = functionAttribute.Schema,
                IsAggregate            = functionAttribute.IsAggregate,
                IsBuiltIn              = functionAttribute.IsBuiltIn,
                IsNiladic              = functionAttribute.IsNiladic,
                IsComposable           = functionAttribute.IsComposable,
                ParameterTypeSemantics = functionAttribute.ParameterTypeSemantics,
                Parameters             = model.GetStoreParameters(methodInfo, functionAttribute),
                ReturnParameters       = model.GetStoreReturnParameters(methodInfo, functionAttribute),
                CommandText            = methodInfo.GetStoreCommandText(functionAttribute, functionName),
            },
                null);

            model.StoreModel.AddItem(storeFunction);

            switch (functionAttribute.Type)
            {
            // Aggregate/Built in/Niladic/Composable scalar-valued function has no <FunctionImport> or <FunctionImportMapping>.
            case FunctionType.ComposableScalarValuedFunction:
            case FunctionType.AggregateFunction:
            case FunctionType.BuiltInFunction:
            case FunctionType.NiladicFunction:
            case FunctionType.ModelDefinedFunction:
                return;
            }

            /*
             * <!-- CSDL content -->
             * <edmx:ConceptualModels>
             * <Schema Namespace="AdventureWorks" Alias="Self" annotation:UseStrongSpatialTypes="false" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
             * <EntityContainer Name="AdventureWorks" annotation:LazyLoadingEnabled="true">
             * <FunctionImport Name="ufnGetContactInformation" IsComposable="true" ReturnType="Collection(AdventureWorks.ContactInformation)">
             * <Parameter Name="PersonID" Mode="In" Type="Int32" />
             * </FunctionImport>
             * <FunctionImport Name="uspGetCategoryAndSubCategory" ReturnType="Collection(AdventureWorks.CategoryAndSubCategory)">
             * <Parameter Name="CategoryID" Mode="In" Type="Int32" />
             * </FunctionImport>
             * <FunctionImport Name="uspGetManagerEmployees" ReturnType="Collection(AdventureWorks.ManagerEmployee)">
             * <Parameter Name="BusinessEntityID" Mode="In" Type="Int32" />
             * </FunctionImport>
             * <FunctionImport Name="ufnGetProductStandardCost" ReturnType="Collection(Decimal)">
             * <Parameter Name="ProductID" Mode="In" Type="Int32" />
             * <Parameter Name="OrderDate" Mode="In" Type="DateTime" />
             * </FunctionImport>
             * <FunctionImport Name="ufnGetPersons" IsComposable="true" EntitySet="Persons" ReturnType="Collection(Model.Person)">
             * <Parameter Name="Name" Mode="In" Type="String" />
             * </FunctionImport>
             * </EntityContainer>
             * </Schema>
             * </edmx:ConceptualModels>
             */
            // Build above <ConceptualModels> imperatively.
            EdmFunction modelFunction = EdmFunction.Create(
                storeFunction.Name,
                model.ConceptualModel.Container.Name,
                DataSpace.CSpace, // <edmx:ConceptualModels>
                new EdmFunctionPayload
            {
                IsFunctionImport = true,
                IsComposable     = storeFunction.IsComposableAttribute,
                Parameters       = model.GetModelParameters(methodInfo, storeFunction),
                ReturnParameters = model.GetModelReturnParameters(methodInfo, functionAttribute),
                EntitySets       = model.GetModelEntitySets(methodInfo, functionAttribute)
            },
                null);

            model.ConceptualModel.Container.AddFunctionImport(modelFunction);

            /*
             * <!-- C-S mapping content -->
             * <edmx:Mappings>
             * <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
             * <EntityContainerMapping StorageEntityContainer="CodeFirstDatabase" CdmEntityContainer="AdventureWorks">
             * <FunctionImportMapping FunctionImportName="ufnGetContactInformation" FunctionName="AdventureWorks.ufnGetContactInformation">
             * <ResultMapping>
             * <ComplexTypeMapping TypeName="AdventureWorks.ContactInformation">
             *  <ScalarProperty Name="PersonID" ColumnName="PersonID" />
             *  <ScalarProperty Name="FirstName" ColumnName="FirstName" />
             *  <ScalarProperty Name="LastName" ColumnName="LastName" />
             *  <ScalarProperty Name="JobTitle" ColumnName="JobTitle" />
             *  <ScalarProperty Name="BusinessEntityType" ColumnName="BusinessEntityType" />
             * </ComplexTypeMapping>
             * </ResultMapping>
             * </FunctionImportMapping>
             * <FunctionImportMapping FunctionImportName="uspGetCategoryAndSubCategory" FunctionName="AdventureWorks.uspGetCategoryAndSubCategory">
             * <ResultMapping>
             * <ComplexTypeMapping TypeName="AdventureWorks.CategoryAndSubCategory">
             *  <ScalarProperty Name="ProductCategoryID" ColumnName="ProductCategoryID" />
             *  <ScalarProperty Name="Name" ColumnName="Name" />
             * </ComplexTypeMapping>
             * </ResultMapping>
             * </FunctionImportMapping>
             * <FunctionImportMapping FunctionImportName="uspGetManagerEmployees" FunctionName="AdventureWorks.uspGetManagerEmployees">
             * <ResultMapping>
             * <ComplexTypeMapping TypeName="AdventureWorks.ManagerEmployee">
             *  <ScalarProperty Name="RecursionLevel" ColumnName="RecursionLevel" />
             *  <ScalarProperty Name="OrganizationNode" ColumnName="OrganizationNode" />
             *  <ScalarProperty Name="ManagerFirstName" ColumnName="ManagerFirstName" />
             *  <ScalarProperty Name="ManagerLastName" ColumnName="ManagerLastName" />
             *  <ScalarProperty Name="BusinessEntityID" ColumnName="BusinessEntityID" />
             *  <ScalarProperty Name="FirstName" ColumnName="FirstName" />
             *  <ScalarProperty Name="LastName" ColumnName="LastName" />
             * </ComplexTypeMapping>
             * </ResultMapping>
             * </FunctionImportMapping>
             * <FunctionImportMapping FunctionImportName="ufnGetProductStandardCost" FunctionName="AdventureWorks.ufnGetProductStandardCost" />
             * <FunctionImportMapping FunctionImportName="ufnGetPersons" FunctionName="Model.Store.ufnGetPersons" />
             * </EntityContainerMapping>
             * </Mapping>
             * </edmx:Mappings>
             */
            // Build above <Mappings> imperatively.
            if (modelFunction.IsComposableAttribute)
            {
                model.ConceptualToStoreMapping.AddFunctionImportMapping(new FunctionImportMappingComposable(
                                                                            modelFunction,
                                                                            storeFunction,
                                                                            new FunctionImportResultMapping(),
                                                                            model.ConceptualToStoreMapping));
            }
            else
            {
                model.ConceptualToStoreMapping.AddFunctionImportMapping(new FunctionImportMappingNonComposable(
                                                                            modelFunction,
                                                                            storeFunction,
                                                                            Enumerable.Empty <FunctionImportResultMapping>(),
                                                                            model.ConceptualToStoreMapping));
            }
        }
Example #8
0
        private static PrimitiveType GetStoreParameterPrimitiveType(
            this DbModel model, MethodInfo methodInfo, ParameterInfo parameterInfo, FunctionAttribute functionAttribute)
        {
            // <Parameter Name="PersonID" Type="int" Mode="In" />
            Type parameterClrType = parameterInfo.ParameterType;
            ParameterAttribute parameterAttribute = parameterInfo.GetCustomAttribute <ParameterAttribute>();
            Type parameterAttributeClrType        = parameterAttribute?.ClrType;

            if (parameterClrType.IsGenericType)
            {
                Type parameterClrTypeDefinition = parameterClrType.GetGenericTypeDefinition();
                if (parameterClrTypeDefinition == typeof(IEnumerable <>) ||
                    parameterClrTypeDefinition == typeof(IQueryable <>))
                {
                    if (functionAttribute.Type == FunctionType.AggregateFunction)
                    {
                        // Aggregate function has one IEnumerable<T> or IQueryable<T> parameter.
                        parameterClrType = parameterClrType.GetGenericArguments().Single();
                    }
                    else
                    {
                        throw new NotSupportedException(
                                  $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. {typeof(IEnumerable<>).FullName} parameter must be used for {nameof(FunctionType)}.{nameof(FunctionType.AggregateFunction)} method.");
                    }
                }
            }

            if (parameterClrType == typeof(ObjectParameter))
            {
                // ObjectParameter must be used for stored procedure parameter.
                if (functionAttribute.Type != FunctionType.StoredProcedure)
                {
                    throw new NotSupportedException(
                              $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. {nameof(ObjectParameter)} parameter must be used for {nameof(FunctionType)}.{nameof(FunctionType.StoredProcedure)} method.");
                }

                // ObjectParameter.Type is available only when methodInfo is called.
                // When building model, its store type/clr type must be provided by ParameterAttribute.
                if (parameterAttributeClrType == null)
                {
                    throw new NotSupportedException(
                              $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. {nameof(ObjectParameter)} parameter must have {nameof(ParameterAttribute)} with {nameof(ParameterAttribute.ClrType)} specified, with optional {nameof(ParameterAttribute.DbType)}.");
                }

                parameterClrType = parameterAttributeClrType;
            }
            else
            {
                // When parameter is not ObjectParameter, ParameterAttribute.ClrType should be either not specified, or the same as parameterClrType.
                if (parameterAttributeClrType != null && parameterAttributeClrType != parameterClrType)
                {
                    throw new NotSupportedException(
                              $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. It is of {parameterClrType.FullName} type, but its {nameof(ParameterAttribute)}.{nameof(ParameterAttribute.ClrType)} has a different type {parameterAttributeClrType.FullName}");
                }
            }

            string storePrimitiveTypeName = parameterAttribute?.DbType;

            return(!string.IsNullOrEmpty(storePrimitiveTypeName)
                ? model.GetStorePrimitiveType(storePrimitiveTypeName, methodInfo, parameterInfo)
                : model.GetStorePrimitiveType(parameterClrType, methodInfo, parameterInfo));
        }
        private static IList<EntitySet> GetModelEntitySets(this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute)
        {
            ParameterInfo returnParameterInfo = methodInfo.ReturnParameter;
            if (returnParameterInfo == null || returnParameterInfo.ParameterType == typeof(void))
            {
                throw new NotSupportedException($"The return parameter type of {methodInfo.Name} is not supported.");
            }

            if (functionAttribute.Type == FunctionType.StoredProcedure
                && returnParameterInfo.ParameterType != typeof(int))
            {
                // returnParameterInfo.ParameterType is ObjectResult<T>.
                Type[] returnParameterClrTypes = methodInfo.GetStoredProcedureReturnTypes().ToArray();
                if (returnParameterClrTypes.Length > 1)
                {
                    // Stored procedure has more than one result. 
                    // EdmFunctionPayload.EntitySets must be provided. Otherwise, an ArgumentException will be thrown:
                    // The EntitySets parameter must not be null for functions that return multiple result sets.
                    return returnParameterClrTypes.Select(clrType =>
                    {
                        EntitySet modelEntitySet = model
                            .ConceptualModel
                            .Container
                            .EntitySets
                            .FirstOrDefault(entitySet =>
                                entitySet.ElementType.FullName.EqualsOrdinal(clrType.FullName));
                        if (modelEntitySet == null)
                        {
                            throw new NotSupportedException(
                                $"{clrType.FullName} for method {methodInfo.Name} is not supported in conceptual model as entity set.");
                        }

                        return modelEntitySet;
                    }).ToArray();
                }
            }

            // Do not return new EntitySet[0], which causes a ArgumentException:
            // The number of entity sets should match the number of return parameters.
            return null;
        }
        private static IList<FunctionParameter> GetModelReturnParameters(
            this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute)
        {
            ParameterInfo returnParameterInfo = methodInfo.ReturnParameter;
            if (returnParameterInfo == null || returnParameterInfo.ParameterType == typeof(void))
            {
                throw new NotSupportedException($"The return parameter type of {methodInfo.Name} is not supported.");
            }

            ParameterAttribute returnParameterAttribute = returnParameterInfo.GetCustomAttribute<ParameterAttribute>();
            ResultTypeAttribute[] returnTypeAttributes = methodInfo.GetCustomAttributes<ResultTypeAttribute>().ToArray();
            IEnumerable<EdmType> modelReturnParameterEdmTypes;
            if (functionAttribute.Type == FunctionType.StoredProcedure)
            {
                if (returnParameterAttribute != null)
                {
                    throw new NotSupportedException(
                        $"{nameof(ParameterAttribute)} for method {methodInfo.Name} is not supported.");
                }

                modelReturnParameterEdmTypes = methodInfo
                    .GetStoredProcedureReturnTypes()
                    .Select(clrType => model.GetModelStructualType(clrType, methodInfo));
            }
            else
            {
                if (returnTypeAttributes.Any())
                {
                    throw new NotSupportedException(
                        $"{nameof(ResultTypeAttribute)} for method {methodInfo.Name} is not supported.");
                }

                if (functionAttribute.Type == FunctionType.TableValuedFunction)
                {
                    // returnParameterInfo.ParameterType is IQueryable<T>.
                    Type returnParameterClrType = returnParameterInfo.ParameterType.GetGenericArguments().Single();
                    StructuralType modelReturnParameterStructuralType = model.GetModelStructualType(
                        returnParameterClrType, methodInfo);
                    modelReturnParameterEdmTypes = Enumerable.Repeat(modelReturnParameterStructuralType, 1);
                }
                else
                {
                    Type returnParameterClrType = returnParameterInfo.ParameterType;
                    Type returnParameterAttributeClrType = returnParameterAttribute?.ClrType;
                    if (returnParameterAttributeClrType != null
                        && returnParameterAttributeClrType != returnParameterClrType)
                    {
                        throw new NotSupportedException(
                            $"Return parameter of method {methodInfo.Name} is of {returnParameterClrType.FullName}, but its {nameof(ParameterAttribute)}.{nameof(ParameterAttribute.ClrType)} has a fifferent type {returnParameterAttributeClrType.FullName}");
                    }

                    PrimitiveType returnParameterPrimitiveType = model.GetModelPrimitiveType(
                        returnParameterClrType, methodInfo);
                    modelReturnParameterEdmTypes = Enumerable.Repeat(returnParameterPrimitiveType, 1);
                }
            }

            return modelReturnParameterEdmTypes
                .Select((edmType, index) => FunctionParameter.Create(
                    $"ReturnType{index}",
                    edmType.GetCollectionType(),
                    ParameterMode.ReturnValue))
                .ToArray();
        }
        public static void AddFunction(
            this DbModel model,
            MethodInfo methodInfo,
            FunctionAttribute functionAttribute)
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            if (methodInfo == null)
            {
                throw new ArgumentNullException(nameof(methodInfo));
            }

            if (functionAttribute == null)
            {
                throw new ArgumentNullException(nameof(functionAttribute));
            }

            /*
    <!-- SSDL content -->
    <edmx:StorageModels>
      <Schema Namespace="CodeFirstDatabaseSchema" Provider="System.Data.SqlClient" ProviderManifestToken="2012" Alias="Self" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
        <Function Name="ufnGetContactInformation" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
          <Parameter Name="PersonID" Type="int" Mode="In" />
          <ReturnType>
            <CollectionType>
              <RowType>
                <Property Name="PersonID" Type="int" Nullable="false" />
                <Property Name="FirstName" Type="nvarchar" MaxLength="50" />
                <Property Name="LastName" Type="nvarchar" MaxLength="50" />
                <Property Name="JobTitle" Type="nvarchar" MaxLength="50" />
                <Property Name="BusinessEntityType" Type="nvarchar" MaxLength="50" />
              </RowType>
            </CollectionType>
          </ReturnType>
        </Function>
        <Function Name="ufnGetProductListPrice" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" ReturnType="money">
          <Parameter Name="ProductID" Type="int" Mode="In" />
          <Parameter Name="OrderDate" Type="datetime" Mode="In" />
        </Function>
        <Function Name="ufnGetProductStandardCost" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
          <Parameter Name="ProductID" Type="int" Mode="In" />
          <Parameter Name="OrderDate" Type="datetime" Mode="In" />
          <CommandText>
            SELECT [dbo].[ufnGetProductListPrice](@ProductID, @OrderDate)
          </CommandText>
        </Function>
        <Function Name="uspGetCategoryAndSubCategory" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
          <Parameter Name="CategoryID" Type="int" Mode="In" />
        </Function>
        <Function Name="uspGetManagerEmployees" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
          <Parameter Name="BusinessEntityID" Type="int" Mode="In" />
        </Function>
        <EntityContainer Name="CodeFirstDatabase">
        </EntityContainer>
      </Schema>
    </edmx:StorageModels>
            */
            // Build above <StorageModels> imperatively.
            string functionName = functionAttribute.FunctionName;
            if (string.IsNullOrWhiteSpace(functionName))
            {
                functionName = methodInfo.Name;
            }

            EdmFunction storeFunction = EdmFunction.Create(
                functionName,
                CodeFirstDatabaseSchema, // model.StoreModel.Container.Name is "CodeFirstDatabase".
                DataSpace.SSpace, // <edmx:StorageModels>
                new EdmFunctionPayload()
                {
                    Schema = functionAttribute.Schema,
                    IsAggregate = functionAttribute.IsAggregate,
                    IsBuiltIn = functionAttribute.IsBuiltIn,
                    IsNiladic = functionAttribute.IsNiladic,
                    IsComposable = functionAttribute.IsComposable,
                    ParameterTypeSemantics = functionAttribute.ParameterTypeSemantics,
                    Parameters = model.GetStoreParameters(methodInfo, functionAttribute),
                    ReturnParameters = model.GetStoreReturnParameters(methodInfo, functionAttribute),
                    CommandText = methodInfo.GetStoreCommandText(functionAttribute, functionName)
                },
                null);
            model.StoreModel.AddItem(storeFunction);

            switch (functionAttribute.Type)
            {
                // Aggregate/Built in/Niladic/Composable scalar-valued function has no <FunctionImport> or <FunctionImportMapping>.
                case FunctionType.ComposableScalarValuedFunction:
                case FunctionType.AggregateFunction:
                case FunctionType.BuiltInFunction:
                case FunctionType.NiladicFunction:
                    return;
            }

            /*
    <!-- CSDL content -->
    <edmx:ConceptualModels>
      <Schema Namespace="AdventureWorks" Alias="Self" annotation:UseStrongSpatialTypes="false" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
        <EntityContainer Name="AdventureWorks" annotation:LazyLoadingEnabled="true">
          <FunctionImport Name="ufnGetContactInformation" IsComposable="true" ReturnType="Collection(AdventureWorks.ContactInformation)">
            <Parameter Name="PersonID" Mode="In" Type="Int32" />
          </FunctionImport>
          <FunctionImport Name="uspGetCategoryAndSubCategory" ReturnType="Collection(AdventureWorks.CategoryAndSubCategory)">
            <Parameter Name="CategoryID" Mode="In" Type="Int32" />
          </FunctionImport>
          <FunctionImport Name="uspGetManagerEmployees" ReturnType="Collection(AdventureWorks.ManagerEmployee)">
            <Parameter Name="BusinessEntityID" Mode="In" Type="Int32" />
          </FunctionImport>
          <FunctionImport Name="ufnGetProductStandardCost" ReturnType="Collection(Decimal)">
            <Parameter Name="ProductID" Mode="In" Type="Int32" />
            <Parameter Name="OrderDate" Mode="In" Type="DateTime" />
          </FunctionImport>
        </EntityContainer>
      </Schema>
    </edmx:ConceptualModels>
            */
            // Build above <ConceptualModels> imperatively.
            EdmFunction modelFunction = EdmFunction.Create(
                storeFunction.Name,
                model.ConceptualModel.Container.Name,
                DataSpace.CSpace, // <edmx:ConceptualModels>
                new EdmFunctionPayload
                {
                    IsFunctionImport = true,
                    IsComposable = storeFunction.IsComposableAttribute,
                    Parameters = model.GetModelParameters(methodInfo, storeFunction),
                    ReturnParameters = model.GetModelReturnParameters(methodInfo, functionAttribute),
                    EntitySets = model.GetModelEntitySets(methodInfo, functionAttribute)
                },
                null);
            model.ConceptualModel.Container.AddFunctionImport(modelFunction);

            /*
    <!-- C-S mapping content -->
    <edmx:Mappings>
      <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
        <EntityContainerMapping StorageEntityContainer="CodeFirstDatabase" CdmEntityContainer="AdventureWorks">
          <FunctionImportMapping FunctionImportName="ufnGetContactInformation" FunctionName="AdventureWorks.ufnGetContactInformation">
            <ResultMapping>
              <ComplexTypeMapping TypeName="AdventureWorks.ContactInformation">
                <ScalarProperty Name="PersonID" ColumnName="PersonID" />
                <ScalarProperty Name="FirstName" ColumnName="FirstName" />
                <ScalarProperty Name="LastName" ColumnName="LastName" />
                <ScalarProperty Name="JobTitle" ColumnName="JobTitle" />
                <ScalarProperty Name="BusinessEntityType" ColumnName="BusinessEntityType" />
              </ComplexTypeMapping>
            </ResultMapping>
          </FunctionImportMapping>
          <FunctionImportMapping FunctionImportName="uspGetCategoryAndSubCategory" FunctionName="AdventureWorks.uspGetCategoryAndSubCategory">
            <ResultMapping>
              <ComplexTypeMapping TypeName="AdventureWorks.CategoryAndSubCategory">
                <ScalarProperty Name="ProductCategoryID" ColumnName="ProductCategoryID" />
                <ScalarProperty Name="Name" ColumnName="Name" />
              </ComplexTypeMapping>
            </ResultMapping>
          </FunctionImportMapping>
          <FunctionImportMapping FunctionImportName="uspGetManagerEmployees" FunctionName="AdventureWorks.uspGetManagerEmployees">
            <ResultMapping>
              <ComplexTypeMapping TypeName="AdventureWorks.ManagerEmployee">
                <ScalarProperty Name="RecursionLevel" ColumnName="RecursionLevel" />
                <ScalarProperty Name="OrganizationNode" ColumnName="OrganizationNode" />
                <ScalarProperty Name="ManagerFirstName" ColumnName="ManagerFirstName" />
                <ScalarProperty Name="ManagerLastName" ColumnName="ManagerLastName" />
                <ScalarProperty Name="BusinessEntityID" ColumnName="BusinessEntityID" />
                <ScalarProperty Name="FirstName" ColumnName="FirstName" />
                <ScalarProperty Name="LastName" ColumnName="LastName" />
              </ComplexTypeMapping>
            </ResultMapping>
          </FunctionImportMapping>
          <FunctionImportMapping FunctionImportName="ufnGetProductStandardCost" FunctionName="AdventureWorks.ufnGetProductStandardCost" />
        </EntityContainerMapping>
      </Mapping>
    </edmx:Mappings>
            */
            // Build above <Mappings> imperatively.
            FunctionImportMapping mapping;
            if (modelFunction.IsComposableAttribute)
            {
                mapping = new FunctionImportMappingComposable(
                    modelFunction,
                    storeFunction,
                    new FunctionImportResultMapping(),
                    model.ConceptualToStoreMapping);
            }
            else
            {
                mapping = new FunctionImportMappingNonComposable(
                    modelFunction,
                    storeFunction,
                    Enumerable.Empty<FunctionImportResultMapping>(),
                    model.ConceptualToStoreMapping);
            }

            model.ConceptualToStoreMapping.AddFunctionImportMapping(mapping);
        }
        private static IList<FunctionParameter> GetStoreReturnParameters(
            this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute)
        {
            ParameterInfo returnParameterInfo = methodInfo.ReturnParameter;
            if (returnParameterInfo == null || returnParameterInfo.ParameterType == typeof(void))
            {
                throw new NotSupportedException($"The return type of {methodInfo.Name} is not supported.");
            }

            ParameterAttribute returnParameterAttribute = returnParameterInfo.GetCustomAttribute<ParameterAttribute>();
            ResultTypeAttribute[] returnTypeAttributes = methodInfo.GetCustomAttributes<ResultTypeAttribute>().ToArray();

            if (functionAttribute.Type == FunctionType.StoredProcedure)
            {
                if (returnParameterAttribute != null)
                {
                    throw new NotSupportedException(
                        $"{nameof(ParameterAttribute)} for return value of method {methodInfo.Name} is not supported.");
                }

                return new FunctionParameter[0];
            }

            if (returnTypeAttributes.Any())
            {
                throw new NotSupportedException($"{nameof(ResultTypeAttribute)} for method {methodInfo.Name} is not supported.");
            }

            if (functionAttribute.Type == FunctionType.TableValuedFunction)
            {
                if (returnParameterAttribute != null)
                {
                    throw new NotSupportedException(
                        $"{nameof(ParameterAttribute)} for return value of method {methodInfo.Name} is not supported.");
                }

                /*
        <CollectionType>
          <RowType>
            <Property Name="PersonID" Type="int" Nullable="false" />
            <Property Name="FirstName" Type="nvarchar" MaxLength="50" />
            <Property Name="LastName" Type="nvarchar" MaxLength="50" />
            <Property Name="JobTitle" Type="nvarchar" MaxLength="50" />
            <Property Name="BusinessEntityType" Type="nvarchar" MaxLength="50" />
          </RowType>
        </CollectionType>
                */
                // returnParameterInfo.ParameterType is IQueryable<T>.
                Type storeReturnParameterClrType = returnParameterInfo.ParameterType.GetGenericArguments().Single();
                StructuralType modelReturnParameterStructuralType = model.GetModelStructualType(
                    storeReturnParameterClrType, methodInfo);
                RowType storeReturnParameterRowType = RowType.Create(
                    modelReturnParameterStructuralType
                        .Members
                        .Select(member => EdmProperty.Create(
                            member.Name, model.ProviderManifest.GetStoreType(member.TypeUsage))),
                    null); // Collection of RowType.
                return new FunctionParameter[]
                    {
                        FunctionParameter.Create(
                            "ReturnType",
                            storeReturnParameterRowType.GetCollectionType(),
                            ParameterMode.ReturnValue)
                    };
            }

            if (functionAttribute.Type == FunctionType.NonComposableScalarValuedFunction)
            {
                // Non-composable scalar-valued function.
                return new FunctionParameter[0];
            }

            // Composable scalar-valued/Aggregate/Built in/Niladic function.
            // <Function Name="ufnGetProductListPrice" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" 
            //    ReturnType ="money">
            PrimitiveType storeReturnParameterPrimitiveType = model.GetStoreParameterPrimitiveType(
                    methodInfo, returnParameterInfo, functionAttribute);
            return new FunctionParameter[]
                {
                    FunctionParameter.Create(
                        "ReturnType", storeReturnParameterPrimitiveType, ParameterMode.ReturnValue)
                };
        }
        private static PrimitiveType GetStoreParameterPrimitiveType(
            this DbModel model, MethodInfo methodInfo, ParameterInfo parameterInfo, FunctionAttribute functionAttribute)
        {
            // <Parameter Name="PersonID" Type="int" Mode="In" />
            Type parameterClrType = parameterInfo.ParameterType;
            ParameterAttribute parameterAttribute = parameterInfo.GetCustomAttribute<ParameterAttribute>();
            Type parameterAttributeClrType = parameterAttribute?.ClrType;

            if (parameterClrType.IsGenericType)
            {
                Type parameterClrTypeDefinition = parameterClrType.GetGenericTypeDefinition();
                if (parameterClrTypeDefinition == typeof(IEnumerable<>)
                    || parameterClrTypeDefinition == typeof(IQueryable<>))
                {
                    if (functionAttribute.Type == FunctionType.AggregateFunction)
                    {
                        // Aggregate function has one IEnumerable<T> or IQueryable<T> parameter. 
                        parameterClrType = parameterClrType.GetGenericArguments().Single();
                    }
                    else
                    {
                        throw new NotSupportedException(
                            $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. {typeof(IEnumerable<>).FullName} parameter must be used for {nameof(FunctionType)}.{nameof(FunctionType.AggregateFunction)} method.");
                    }
                }
            }

            if (parameterClrType == typeof(ObjectParameter))
            {
                // ObjectParameter must be used for stored procedure parameter.
                if (functionAttribute.Type != FunctionType.StoredProcedure)
                {
                    throw new NotSupportedException(
                        $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. {nameof(ObjectParameter)} parameter must be used for {nameof(FunctionType)}.{nameof(FunctionType.StoredProcedure)} method.");
                }

                // ObjectParameter.Type is available only when methodInfo is called. 
                // When building model, its store type/clr type must be provided by ParameterAttribute.
                if (parameterAttributeClrType == null)
                {
                    throw new NotSupportedException(
                        $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. {nameof(ObjectParameter)} parameter must have {nameof(ParameterAttribute)} with {nameof(ParameterAttribute.ClrType)} specified, with optional {nameof(ParameterAttribute.DbType)}.");
                }

                parameterClrType = parameterAttributeClrType;
            }
            else
            {
                // When parameter is not ObjectParameter, ParameterAttribute.ClrType should be either not specified, or the same as parameterClrType.
                if (parameterAttributeClrType != null && parameterAttributeClrType != parameterClrType)
                {
                    throw new NotSupportedException(
                        $"Parameter {parameterInfo.Name} of method {methodInfo.Name} is not supported. It is of {parameterClrType.FullName} type, but its {nameof(ParameterAttribute)}.{nameof(ParameterAttribute.ClrType)} has a fifferent type {parameterAttributeClrType.FullName}");
                }
            }

            string storePrimitiveTypeName = parameterAttribute?.DbType;
            return !string.IsNullOrEmpty(storePrimitiveTypeName)
                ? model.GetStorePrimitiveType(storePrimitiveTypeName, methodInfo, parameterInfo)
                : model.GetStorePrimitiveType(parameterClrType, methodInfo, parameterInfo);
        }
        private static IList<FunctionParameter> GetStoreParameters
            (this DbModel model, MethodInfo methodInfo, FunctionAttribute functionAttribute) => methodInfo
                .GetParameters()
                .Select((parameterInfo, index) =>
                    {
                        string parameterName = parameterInfo.GetCustomAttribute<ParameterAttribute>()?.Name;
                        if (string.IsNullOrWhiteSpace(parameterName))
                        {
                            parameterName = parameterInfo.Name;
                        }

                        switch (functionAttribute.Type)
                        {
                            case FunctionType.NiladicFunction:
                                throw new NotSupportedException(
                                    $"Parameter of method {methodInfo.Name} is not supporteds.");

                            case FunctionType.AggregateFunction:
                                {
                                    if (index == 0)
                                    {
                                        return FunctionParameter.Create(
                                            parameterName,
                                            model.GetStoreParameterPrimitiveType(
                                                    methodInfo, parameterInfo, functionAttribute)
                                                .GetCollectionType(), // Must be collection type.
                                            ParameterMode.In);
                                    }

                                    // Aggregate function with more than more parameter is not supported by entity framework.
                                    throw new NotSupportedException(
                                        $"Method {methodInfo.Name} has more than one parameters and is not supported by Entity Framework.");
                                }
                        }

                        return FunctionParameter.Create(
                            parameterName,
                            model.GetStoreParameterPrimitiveType(methodInfo, parameterInfo, functionAttribute),
                            parameterInfo.ParameterType == typeof(ObjectParameter)
                                ? ParameterMode.InOut
                                : ParameterMode.In);
                    })
                .ToArray();