public 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); }
public 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); }
public 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()); }
public 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 => 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) }); }
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, StoreFunctionName = functionAttribute.StoreFunctionName, 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)); } }
public 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)); }