Example #1
0
        /// <summary>
        /// Generates sync or async  stored procedure mapping method.
        /// </summary>
        /// <param name="storedProcedure">Stored procedure model.</param>
        /// <param name="dataContextType">Data context class type.</param>
        /// <param name="methodsGroup">Method group to add new mapping method.</param>
        /// <param name="useOrdinalMapping">If <c>true</c>, by-ordinal mapping used for result mapping instead of by-name mapping.</param>
        /// <param name="customTable">Custom result record model.</param>
        /// <param name="returnElementType">Type of result record for procedure with result.</param>
        /// <param name="customRecordProperties">Column properties for custom result record type.</param>
        /// <param name="classes">Procedure classes group.</param>
        /// <param name="asyncResult">Optional result class model for async signature.</param>
        /// <param name="async">If <c>true</c>, generate async version of mapping.</param>
        private void BuildStoredProcedureMethod(
            StoredProcedureModel storedProcedure,
            IType dataContextType,
            MethodGroup methodsGroup,
            bool useOrdinalMapping,
            ResultTableModel?customTable,
            IType?returnElementType,
            CodeProperty[]?customRecordProperties,
            ClassGroup classes,
            AsyncProcedureResult?asyncResult,
            bool async)
        {
            var hasParameters = storedProcedure.Parameters.Count > 0 || storedProcedure.Return != null;

            // generate ToList materialization call or mark method async in two cases:
            // - when return type of mapping is List<T>
            // - when procedure has non-input parameters
            var toListRequired        = _options.DataModel.GenerateProcedureResultAsList && storedProcedure.Results.Count == 1 && (storedProcedure.Results[0].Entity != null || storedProcedure.Results[0].CustomTable != null);
            var toListOrAsyncRequired = toListRequired ||
                                        storedProcedure.Return != null ||
                                        storedProcedure.Parameters.Any(p => p.Parameter.Direction != CodeParameterDirection.In);

            // declare mapping method
            var method = DefineMethod(
                methodsGroup,
                storedProcedure.Method,
                async,
                async && toListOrAsyncRequired);

            // declare data context parameter (extension `this` parameter)
            var ctxParam = AST.Parameter(
                // see method notes above regarding type of this parameter
                dataContextType,
                AST.Name(STORED_PROCEDURE_CONTEXT_PARAMETER),
                CodeParameterDirection.In);

            method.Parameter(ctxParam);
            var body = method.Body();

            // array of procedure parameters (DataParameter objects)
            CodeVariable?parametersVar = null;

            CodeAssignmentStatement[]?               parameterRebinds = null;
            Dictionary <FunctionParameterModel, int>?rebindedParametersIndexes = null;

            if (hasParameters)
            {
                var resultParametersCount = storedProcedure.Parameters.Count(p => p.Parameter.Direction != CodeParameterDirection.In) + (storedProcedure.Return != null ? 1 : 0);
                // bindings of parameter values to output parameters of method after procedure call
                parameterRebinds = resultParametersCount > 0 ? new CodeAssignmentStatement[resultParametersCount] : Array <CodeAssignmentStatement> .Empty;
                var rebindIndex = 0;
                if (resultParametersCount > 0)
                {
                    rebindedParametersIndexes = new(resultParametersCount);
                }

                // DataParameter collection initialization
                var parameterValues = new ICodeExpression[storedProcedure.Parameters.Count + (storedProcedure.Return != null ? 1 : 0)];
                parametersVar = AST.Variable(AST.Name(STORED_PROCEDURE_PARAMETERS_VARIABLE), WellKnownTypes.LinqToDB.Data.DataParameterArray, true);

                // build non-return parameters
                for (var i = 0; i < storedProcedure.Parameters.Count; i++)
                {
                    var     p              = storedProcedure.Parameters[i];
                    ILValue?rebindTo       = null;
                    var     rebindRequired = p.Parameter.Direction != CodeParameterDirection.In;

                    CodeParameter param;
                    if (async && p.Parameter.Direction != CodeParameterDirection.In)
                    {
                        param = DefineParameter(method, p.Parameter.WithDirection(CodeParameterDirection.In));
                    }
                    else
                    {
                        param = DefineParameter(method, p.Parameter);
                    }

                    if (rebindRequired)
                    {
                        rebindTo = param.Reference;
                    }

                    parameterValues[i] = BuildProcedureParameter(
                        param,
                        param.Type.Type,
                        p.Direction,
                        rebindTo,
                        p.DbName,
                        p.DataType,
                        p.Type,
                        parametersVar,
                        i,
                        parameterRebinds !,
                        rebindIndex);

                    if (p.Parameter.Direction != CodeParameterDirection.In)
                    {
                        rebindedParametersIndexes !.Add(p, rebindIndex);
                        rebindIndex++;
                    }
                }

                // build return parameter
                if (storedProcedure.Return != null)
                {
                    CodeParameter?param = null;
                    if (!async)
                    {
                        param = DefineParameter(method, storedProcedure.Return.Parameter);
                    }

                    parameterValues[storedProcedure.Parameters.Count] = BuildProcedureParameter(
                        param,
                        storedProcedure.Return.Parameter.Type,
                        System.Data.ParameterDirection.ReturnValue,
                        param?.Reference ?? AST.Variable(AST.Name("fake"), storedProcedure.Return.Parameter.Type, false).Reference,
                        storedProcedure.Return.DbName ?? STORED_PROCEDURE_DEFAULT_RETURN_PARAMETER,
                        storedProcedure.Return.DataType,
                        storedProcedure.Return.Type,
                        parametersVar,
                        storedProcedure.Parameters.Count,
                        parameterRebinds !,
                        rebindIndex);
                    rebindedParametersIndexes !.Add(storedProcedure.Return, rebindIndex);
                }

                var parametersArray = AST.Assign(
                    parametersVar,
                    AST.Array(WellKnownTypes.LinqToDB.Data.DataParameter, true, false, parameterValues));
                body.Append(parametersArray);
            }

            CodeParameter?cancellationTokenParameter = null;

            if (async)
            {
                cancellationTokenParameter = DefineParameter(
                    method,
                    new ParameterModel(CANCELLATION_TOKEN_PARAMETER, WellKnownTypes.System.Threading.CancellationToken, CodeParameterDirection.In),
                    AST.Default(WellKnownTypes.System.Threading.CancellationToken, true));
            }

            ICodeExpression?returnValue = null;

            IType returnType;

            if (storedProcedure.Results.Count == 0 || (storedProcedure.Results.Count == 1 && storedProcedure.Results[0].CustomTable == null && storedProcedure.Results[0].Entity == null))
            {
                // for stored procedure call without result set we use ExecuteProc API
                // prepare call parameters
                var parametersCount       = (hasParameters ? 3 : 2) + (async ? 1 : 0);
                var executeProcParameters = new ICodeExpression[parametersCount];
                executeProcParameters[0] = ctxParam.Reference;
                executeProcParameters[1] = AST.Constant(BuildFunctionName(storedProcedure.Name), true);
                if (async)
                {
                    executeProcParameters[2] = cancellationTokenParameter !.Reference;
                }
                if (hasParameters)
                {
                    executeProcParameters[async ? 3 : 2] = parametersVar !.Reference;
                }

                returnType = WellKnownTypes.System.Int32;
                if (async)
                {
                    returnValue = AST.ExtCall(
                        WellKnownTypes.LinqToDB.Data.DataConnectionExtensions,
                        WellKnownTypes.LinqToDB.Data.DataConnectionExtensions_ExecuteProcAsync,
                        WellKnownTypes.System.Int32,
                        executeProcParameters);

                    if (asyncResult != null)
                    {
                        var rowCountVar = AST.Variable(
                            AST.Name(STORED_PROCEDURE_RESULT_VARIABLE),
                            WellKnownTypes.System.Int32,
                            true);
                        body.Append(AST.Assign(rowCountVar, AST.AwaitExpression(returnValue)));
                        returnValue = rowCountVar.Reference;
                    }
                }
                else
                {
                    returnValue = AST.ExtCall(
                        WellKnownTypes.LinqToDB.Data.DataConnectionExtensions,
                        WellKnownTypes.LinqToDB.Data.DataConnectionExtensions_ExecuteProc,
                        WellKnownTypes.System.Int32,
                        executeProcParameters);
                }
            }
            else if (storedProcedure.Results.Count == 1)
            {
                // QueryProc type- and regular parameters
                IType[]           queryProcTypeArgs;
                ICodeExpression[] queryProcParameters;

                if (useOrdinalMapping)
                {
                    // for ordinal mapping we call QueryProc API with mapper lambda parameter:
                    // manual mapping
                    // Example:

                    /*
                     * dataReader => new CustomResult()
                     * {
                     *     Column1 = Converter.ChangeTypeTo<int>(dataReader.GetValue(0), dataConnection.MappingSchema),
                     *     Column2 = Converter.ChangeTypeTo<string>(dataReader.GetValue(1), dataConnection.MappingSchema),
                     * }
                     */

                    queryProcParameters = new ICodeExpression[(hasParameters ? 4 : 3) + (async ? 1 : 0)];

                    // generate positional mapping lambda
                    // TODO: switch to ColumnReader.GetValue in future to utilize more precise mapping
                    // based on column mapping attributes
                    var drParam = AST.LambdaParameter(
                        AST.Name(STORED_PROCEDURE_CUSTOM_MAPPER_PARAMETER),
                        // TODO: add IDataReader support here for linq2db v3
                        WellKnownTypes.System.Data.Common.DbDataReader);
                    var initializers = new CodeAssignmentStatement[customTable !.Columns.Count];