/// <summary> /// Generates data context constructors. /// </summary> /// <param name="contextBuilder">Data context class builder.</param> /// <param name="initSchemasMethodName">(Optional) additional schemas init method name.</param> private void BuildDataContextConstructors( ClassBuilder contextBuilder, CodeIdentifier?initSchemasMethodName) { var constructors = contextBuilder.Constructors(); var ctors = new List <BlockBuilder>(); // based on selected constructors set we generate (or not) following constructors: // .ctor() // default constructor // .ctor(string configuration) // constructor with connection configuration name parameter // .ctor(LinqToDBConnectionOptions options) // options constructor // .ctor(LinqToDBConnectionOptions<T> options) // typed options constructor // first we generate empty constructors and then add body to all of them as they will have same code for body if (_dataModel.DataContext.HasDefaultConstructor) { ctors.Add(constructors.New().SetModifiers(Modifiers.Public).Body()); } if (_dataModel.DataContext.HasConfigurationConstructor) { var configurationParam = AST.Parameter( WellKnownTypes.System.String, AST.Name(CONTEXT_CONSTRUCTOR_CONFIGURATION_PARAMETER), CodeParameterDirection.In); ctors.Add(constructors .New() .Parameter(configurationParam) .SetModifiers(Modifiers.Public) .Base(configurationParam.Reference) .Body()); } if (_dataModel.DataContext.HasUntypedOptionsConstructor) { var optionsParam = AST.Parameter( WellKnownTypes.LinqToDB.Configuration.LinqToDBConnectionOptions, AST.Name(CONTEXT_CONSTRUCTOR_OPTIONS_PARAMETER), CodeParameterDirection.In); ctors.Add(constructors .New() .Parameter(optionsParam) .SetModifiers(Modifiers.Public) .Base(optionsParam.Reference) .Body()); } if (_dataModel.DataContext.HasTypedOptionsConstructor) { var typedOptionsParam = AST.Parameter( WellKnownTypes.LinqToDB.Configuration.LinqToDBConnectionOptionsWithType(contextBuilder.Type.Type), AST.Name(CONTEXT_CONSTRUCTOR_OPTIONS_PARAMETER), CodeParameterDirection.In); ctors.Add(constructors .New() .Parameter(typedOptionsParam) .SetModifiers(Modifiers.Public) .Base(typedOptionsParam.Reference) .Body()); } // partial init method, called by all constructors, which could be used by user to add // additional initialization logic var initDataContext = contextBuilder .Methods(true) .New(AST.Name(CONTEXT_INIT_METHOD)) .SetModifiers(Modifiers.Partial); foreach (var body in ctors) { // each constructor calls: // InitSchemas(); // for context with additional schemas // InitDataContext(); // partial method for custom initialization if (initSchemasMethodName != null) { body.Append(AST.Call(contextBuilder.Type.This, initSchemasMethodName)); } body.Append(AST.Call(contextBuilder.Type.This, initDataContext.Method.Name)); } }
/// <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 }