Esempio n. 1
0
        /// <summary>
        /// Generates mapping class for specific <paramref name="entity"/>. Also generates Find extension method if its
        /// generation enabled for entity.
        /// </summary>
        /// <param name="entity">Entity data model.</param>
        /// <param name="entityBuilder">Entity class builder.</param>
        /// <param name="contextProperties">Property group in data context with table access properties.</param>
        /// <param name="contextType">Data context class type.</param>
        /// <param name="context">Data context instance accessor for context property generation.</param>
        /// <param name="findMethodsGroup">Action to get Find extension method group.</param>
        private void BuildEntity(
            EntityModel entity,
            ClassBuilder entityBuilder,
            PropertyGroup contextProperties,
            IType contextType,
            ICodeExpression context,
            Func <MethodGroup> findMethodsGroup)
        {
            // generate table metadata for entity
            _metadataBuilder.BuildEntityMetadata(entity.Metadata, entityBuilder);

            // generate colum properties
            var columnsGroup = entityBuilder.Properties(true);

            foreach (var columnModel in entity.Columns)
            {
                var columnBuilder = DefineProperty(columnsGroup, columnModel.Property);

                // register property in lookup for later use by associations generator
                _columnProperties.Add(columnModel, columnBuilder.Property);

                // generate column metadata
                _metadataBuilder.BuildColumnMetadata(columnModel.Metadata, columnBuilder);
            }

            // generate IEquatable interface implementation
            BuildEntityIEquatable(entity, entityBuilder);

            // add entity access property to data context
            BuildEntityContextProperty(entity, entityBuilder.Type.Type, contextProperties, context);

            // generate Find extension method
            BuildFindExtensions(entity, contextType, entityBuilder.Type.Type, findMethodsGroup);
        }
Esempio n. 2
0
        /// <summary>
        /// Generates mapping classes for table/view models (entities).
        /// </summary>
        /// <param name="entities">Collection of entity models.</param>
        /// <param name="defineEntityClass">Action to define new empty entity class.</param>
        /// <param name="contextProperties">Property group in data context with table access properties.</param>
        /// <param name="contextType">Data context class type.</param>
        /// <param name="context">Data context instance accessor for context property generation.</param>
        /// <param name="findMethodsGroup">Action to get Find extension method group.</param>
        private void BuildEntities(
            IReadOnlyCollection <EntityModel> entities,
            Func <EntityModel, ClassBuilder> defineEntityClass,
            PropertyGroup contextProperties,
            IType contextType,
            ICodeExpression context,
            Func <MethodGroup> findMethodsGroup)
        {
            foreach (var entity in entities)
            {
                var entityBuilder = defineEntityClass(entity);

                // register entity class builder in lookup so later we can access it
                // during generation of associations and procedures/functions
                _entityBuilders.Add(entity, entityBuilder);

                BuildEntity(
                    entity,
                    entityBuilder,
                    contextProperties,
                    contextType,
                    context,
                    findMethodsGroup);
            }
        }
Esempio n. 3
0
 public CodeCallStatement(
     bool extension,
     ICodeExpression callee,
     CodeIdentifier method,
     IEnumerable <IType> genericArguments,
     bool skipTypeArguments,
     IEnumerable <ICodeExpression> parameters)
     : base(extension, callee, method, genericArguments.Select(static t => new CodeTypeToken(t)), skipTypeArguments, parameters)
Esempio n. 4
0
        public CodeSuppressNull(ICodeExpression value)
        {
            if (!value.Type.IsNullable)
            {
                throw new InvalidOperationException($"null-forgiving operator cannot be used with non-nullable value: {value.Type}");
            }

            Value = value;
        }
Esempio n. 5
0
 internal CodeCallStatement(
     bool extension,
     ICodeExpression callee,
     CodeIdentifier method,
     IEnumerable <CodeTypeToken> genericArguments,
     bool skipTypeArguments,
     IEnumerable <ICodeExpression> parameters)
     : base(extension, callee, method, genericArguments, skipTypeArguments, parameters)
 {
 }
Esempio n. 6
0
        public CodeAsOperator(CodeTypeToken type, ICodeExpression value)
        {
            if (type.Type.IsValueType && !type.Type.IsNullable)
            {
                throw new InvalidOperationException($"as operator cannot be used with non-nullable value type: {type.Type}");
            }

            Type  = type;
            Value = value;
        }
Esempio n. 7
0
        /// <summary>
        /// Generates array values for <see cref="Sql.TableFunctionAttribute.ArgIndices"/>
        /// or <see cref="Sql.ExpressionAttribute.ArgIndices"/> setter.
        /// </summary>
        /// <param name="argIndices">Array values.</param>
        /// <returns>AST nodes for array values.</returns>
        private ICodeExpression[] BuildArgIndices(int[] argIndices)
        {
            var values = new ICodeExpression[argIndices.Length];

            for (var i = 0; i < argIndices.Length; i++)
            {
                values[i] = _builder.Constant(argIndices[i], true);
            }

            return(values);
        }
Esempio n. 8
0
 internal CodeCallExpression(
     bool extension,
     ICodeExpression callee,
     CodeIdentifier method,
     IEnumerable <CodeTypeToken> genericArguments,
     bool skipTypeArguments,
     IEnumerable <ICodeExpression> parameters,
     IType returnType)
     : base(extension, callee, method, genericArguments, skipTypeArguments, parameters)
 {
     ReturnType = returnType;
 }
Esempio n. 9
0
 protected CodeCallBase(
     bool extension,
     ICodeExpression callee,
     CodeIdentifier method,
     IEnumerable <CodeTypeToken> genericArguments,
     bool skipTypeArguments,
     IEnumerable <ICodeExpression> parameters)
 {
     Extension            = extension;
     Callee               = callee;
     MethodName           = method;
     CanSkipTypeArguments = skipTypeArguments;
     _genericArguments    = new (genericArguments ?? Array <CodeTypeToken> .Empty);
     _parameters          = new (parameters ?? Array <ICodeExpression> .Empty);
 }
Esempio n. 10
0
        public CodeBinary(ICodeExpression left, BinaryOperation operation, ICodeExpression right)
        {
            Left      = left;
            Operation = operation;
            Right     = right;

            switch (operation)
            {
            case BinaryOperation.Equal:
            case BinaryOperation.And:
                _type = WellKnownTypes.System.Boolean;
                break;

            case BinaryOperation.Add:
                // this is not correct in general
                // but for now we will stick to it while it doesn't give issues
                _type = left.Type;
                break;

            default:
                throw new NotImplementedException($"Type infer is not implemented for binary operation: {operation}");
            }
        }
Esempio n. 11
0
 public CodeTypeCast(IType type, ICodeExpression value)
     : this(new CodeTypeToken(type), value)
 {
 }
Esempio n. 12
0
 public CodeAwaitExpression(ICodeExpression task)
 {
     Task = task;
 }
Esempio n. 13
0
 public CodeAssignmentExpression(ILValue lvalue, ICodeExpression rvalue)
     : base(lvalue, rvalue)
 {
 }
Esempio n. 14
0
 /// <summary>
 /// Add field initializer.
 /// </summary>
 /// <param name="initializer">Initializer expression.</param>
 /// <returns>Builder instance.</returns>
 public FieldBuilder AddInitializer(ICodeExpression initializer)
 {
     Field.Initializer = initializer;
     return(this);
 }
Esempio n. 15
0
 /// <summary>
 /// Create instance member access expression.
 /// </summary>
 /// <param name="instance">Member owner instance.</param>
 /// <param name="member">Member reference.</param>
 public CodeMember(ICodeExpression instance, CodeReference member)
 {
     Instance = instance;
     Member   = member;
 }
Esempio n. 16
0
 public CodeIndex(ICodeExpression @object, ICodeExpression index, IType returnType)
 {
     Object     = @object;
     Index      = index;
     ReturnType = returnType;
 }
Esempio n. 17
0
 public CodeAssignmentBase(ILValue lvalue, ICodeExpression rvalue)
 {
     LValue = lvalue;
     RValue = rvalue;
 }
Esempio n. 18
0
 public CodeNameOf(ICodeExpression expression)
 {
     Expression = expression;
 }
Esempio n. 19
0
 public CodeThrowStatement(ICodeExpression exception)
     : base(exception)
 {
 }
Esempio n. 20
0
 /// <summary>
 /// Add property initializer.
 /// </summary>
 /// <param name="initializer">Initialization expression.</param>
 /// <returns>Builder instance.</returns>
 public PropertyBuilder SetInitializer(ICodeExpression initializer)
 {
     Property.Initializer = initializer;
     return(this);
 }
Esempio n. 21
0
 public record CodeNamedParameter(CodeIdentifier Property, ICodeExpression Value);
Esempio n. 22
0
 public CodeAwaitStatement(ICodeExpression task)
 {
     Task = task;
 }
Esempio n. 23
0
 public CodeTypeCast(CodeTypeToken type, ICodeExpression value)
 {
     Type  = type;
     Value = value;
 }
Esempio n. 24
0
 public CodeAsOperator(IType type, ICodeExpression value)
     : this(new CodeTypeToken(type), value)
 {
 }
Esempio n. 25
0
 /// <summary>
 /// Add named parameter value.
 /// </summary>
 /// <param name="property">Attribute property name.</param>
 /// <param name="value">Parameter value.</param>
 /// <returns>Builder instance.</returns>
 public AttributeBuilder Parameter(CodeIdentifier property, ICodeExpression value)
 {
     Attribute.AddNamedParameter(property, value);
     return(this);
 }
Esempio n. 26
0
 /// <summary>
 /// Add positional parameter value.
 /// </summary>
 /// <param name="value">Parameter value.</param>
 /// <returns>Builder instance.</returns>
 public AttributeBuilder Parameter(ICodeExpression value)
 {
     Attribute.AddParameter(value);
     return(this);
 }
        /// <summary>
        /// Generates table function mapping.
        /// </summary>
        /// <param name="tableFunction">Function model.</param>
        /// <param name="functionsGroup">Functions region.</param>
        /// <param name="context">Data context class.</param>
        private void BuildTableFunction(
            TableFunctionModel tableFunction,
            RegionGroup functionsGroup,
            CodeClass context)
        {
            // generated code sample:

            /*
             * #region Function1
             * private static readonly MethodInfo _function1 = MemberHelper.MethodOf<DataContext>(ctx => ctx.Function1(default));
             *
             * [Sql.TableFunction("Function1")]
             * public IQueryable<Parent> Function1(int? id)
             * {
             * return this.GetTable<Parent>(this, _function1, id);
             * }
             * #endregion
             */

            // create function region
            var region = functionsGroup.New(tableFunction.Method.Name);

            // if function schema load failed, generate error pragma with exception details
            if (tableFunction.Error != null)
            {
                if (_options.DataModel.GenerateProceduresSchemaError)
                {
                    region.Pragmas().Add(AST.Error($"Failed to load return table schema: {tableFunction.Error}"));
                }

                // as we cannot generate table function without knowing it's schema, we skip failed function
                return;
            }

            // if function result schema matches known entity, we use entity class for result
            // otherwise we generate custom record mapping
            if (tableFunction.Result == null || tableFunction.Result.CustomTable == null && tableFunction.Result.Entity == null)
            {
                throw new InvalidOperationException($"Table function {tableFunction.Name} result record type not set");
            }

            // GetTable API for table functions need MethodInfo instance of generated method as parameter
            // to not load it on each call, we cache MethodInfo instance in static field
            var methodInfo = region
                             .Fields(false)
                             .New(AST.Name(tableFunction.MethodInfoFieldName), WellKnownTypes.System.Reflection.MethodInfo)
                             .Private()
                             .Static()
                             .ReadOnly();

            // generate mapping method with metadata
            var method = DefineMethod(region.Methods(false), tableFunction.Method);

            _metadataBuilder.BuildTableFunctionMetadata(tableFunction.Metadata, method);

            // generate method parameters, return type and body

            // table record type
            IType returnEntity;

            if (tableFunction.Result.Entity != null)
            {
                returnEntity = _entityBuilders[tableFunction.Result.Entity].Type.Type;
            }
            else
            {
                returnEntity = BuildCustomResultClass(tableFunction.Result.CustomTable !, region.Classes(), true).resultClassType;
            }

            // set return type
            // T4 used ITable<T> for return type, but there is no reason to use ITable<T> over IQueryable<T>
            // Even more: ITable<T> is not correct return type here
            var returnType = _options.DataModel.TableFunctionReturnsTable
                                ? WellKnownTypes.LinqToDB.ITable(returnEntity)
                                : WellKnownTypes.System.Linq.IQueryable(returnEntity);

            method.Returns(returnType);

            // parameters for GetTable call in mapping body
            var parameters = new ICodeExpression[3 + tableFunction.Parameters.Count];

            parameters[0] = context.This;               // `this` extension method parameter
            parameters[1] = context.This;               // context parameter
            parameters[2] = methodInfo.Field.Reference; // method info field

            // add table function parameters (if any)
            var fieldInitParameters = new ICodeExpression[tableFunction.Parameters.Count];

            for (var i = 0; i < tableFunction.Parameters.Count; i++)
            {
                var param = tableFunction.Parameters[i];
                // parameters added to 3 places:
                // - to mapping method
                // - to GetTable call in mapping
                // - to mapping call in MethodInfo initializer we add parameter's default value
                var parameter = DefineParameter(method, param.Parameter);
                parameters[i + 3] = parameter.Reference;
                // TODO: potential issue: target-typed `default` could cause errors with overloads
                fieldInitParameters[i] = AST.Default(param.Parameter.Type, true);
            }

            // generate mapping body
            method.Body()
            .Append(
                AST.Return(
                    AST.ExtCall(
                        WellKnownTypes.LinqToDB.DataExtensions,
                        WellKnownTypes.LinqToDB.DataExtensions_GetTable,
                        WellKnownTypes.LinqToDB.ITable(returnEntity),
                        new[] { returnEntity },
                        false,
                        parameters)));

            // generate MethodInfo field initializer
            var lambdaParam = AST.LambdaParameter(AST.Name(TABLE_FUNCTION_METHOD_INFO_CONTEXT_PARAMETER), context.Type);

            // Expression<Func<context, returnType>>
            var lambda = AST
                         .Lambda(WellKnownTypes.System.Linq.Expressions.Expression(WellKnownTypes.System.Func(returnType, context.Type)), true)
                         .Parameter(lambdaParam);

            lambda.Body()
            .Append(
                AST.Return(
                    AST.Call(
                        lambdaParam.Reference,
                        method.Method.Name,
                        returnType,
                        fieldInitParameters)));

            methodInfo.AddInitializer(
                AST.Call(
                    new CodeTypeReference(WellKnownTypes.LinqToDB.Expressions.MemberHelper),
                    WellKnownTypes.LinqToDB.Expressions.MemberHelper_MethodOf,
                    WellKnownTypes.System.Reflection.MethodInfo,
                    new[] { functionsGroup.OwnerType.Type },
                    false,
                    lambda.Method));

            // TODO: similar tables
        }
Esempio n. 28
0
		public CodeThrowBase(ICodeExpression exception)
		{
			Exception = exception;
		}
Esempio n. 29
0
 public CodeThrowExpression(ICodeExpression exception, IType targetType)
     : base(exception)
 {
     _targetType = targetType;
 }
Esempio n. 30
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];