/// <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); }
/// <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); } }
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)
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; }
internal CodeCallStatement( bool extension, ICodeExpression callee, CodeIdentifier method, IEnumerable <CodeTypeToken> genericArguments, bool skipTypeArguments, IEnumerable <ICodeExpression> parameters) : base(extension, callee, method, genericArguments, skipTypeArguments, parameters) { }
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; }
/// <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); }
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; }
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); }
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}"); } }
public CodeTypeCast(IType type, ICodeExpression value) : this(new CodeTypeToken(type), value) { }
public CodeAwaitExpression(ICodeExpression task) { Task = task; }
public CodeAssignmentExpression(ILValue lvalue, ICodeExpression rvalue) : base(lvalue, rvalue) { }
/// <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); }
/// <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; }
public CodeIndex(ICodeExpression @object, ICodeExpression index, IType returnType) { Object = @object; Index = index; ReturnType = returnType; }
public CodeAssignmentBase(ILValue lvalue, ICodeExpression rvalue) { LValue = lvalue; RValue = rvalue; }
public CodeNameOf(ICodeExpression expression) { Expression = expression; }
public CodeThrowStatement(ICodeExpression exception) : base(exception) { }
/// <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); }
public record CodeNamedParameter(CodeIdentifier Property, ICodeExpression Value);
public CodeAwaitStatement(ICodeExpression task) { Task = task; }
public CodeTypeCast(CodeTypeToken type, ICodeExpression value) { Type = type; Value = value; }
public CodeAsOperator(IType type, ICodeExpression value) : this(new CodeTypeToken(type), value) { }
/// <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); }
/// <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 }
public CodeThrowBase(ICodeExpression exception) { Exception = exception; }
public CodeThrowExpression(ICodeExpression exception, IType targetType) : base(exception) { _targetType = targetType; }
/// <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];