public static SchemaMapping[] Compile(DataContextModel model) { var options = new CompilerParameters(References.Select(a => a.Location).ToArray()) { GenerateInMemory = true }; try { var sourceFile = options.TempFiles.AddExtension("cs"); using (var writer = new StreamWriter(sourceFile)) model.WriteClasses(writer); var compiler = new CSharpCodeProvider(new Dictionary <string, string> { { "CompilerVersion", "v4.0" } }); var results = compiler.CompileAssemblyFromFile(options, sourceFile); if (results.Errors.Count > 0) { throw new InvalidOperationException(results.Errors.Cast <CompilerError>().Join(Environment.NewLine, ce => ce.ErrorText)); } return (model.Schemas.Select(s => (SchemaMapping) results.CompiledAssembly .GetType(model.Namespace + "." + s.RowClassName) .GetProperty("SchemaMapping").GetValue(null, null) ).SortDependencies(sm => sm.Schema) .ToArray()); } finally { options.TempFiles.Delete(); } }
/// <summary> /// Return existing or define new model for additional schema. /// </summary> /// <param name="dataContext">Data context model.</param> /// <param name="schemaName">Schema name.</param> /// <returns>Additional schema model.</returns> private AdditionalSchemaModel GetOrAddAdditionalSchema(DataContextModel dataContext, string schemaName) { if (!dataContext.AdditionalSchemas.TryGetValue(schemaName, out var schemaModel)) { if (!_options.DataModel.SchemaMap.TryGetValue(schemaName, out var baseName)) { baseName = schemaName; } var schemaClassName = _namingServices.NormalizeIdentifier( _options.DataModel.SchemaClassNameOptions, baseName); var contextPropertyName = _namingServices.NormalizeIdentifier( _options.DataModel.SchemaPropertyNameOptions, baseName); var wrapperClass = new ClassModel(_options.CodeGeneration.ClassPerFile ? schemaClassName : dataContext.Class.FileName !, schemaClassName) { Modifiers = Modifiers.Public | Modifiers.Partial | Modifiers.Static, Namespace = _options.CodeGeneration.Namespace }; var contextClass = new ClassModel("DataContext") { Modifiers = Modifiers.Public | Modifiers.Partial }; schemaModel = new AdditionalSchemaModel(contextPropertyName, wrapperClass, contextClass); dataContext.AdditionalSchemas.Add(schemaName, schemaModel); } return(schemaModel); }
/// <summary> /// Generates aggregate function model from schema data. /// </summary> /// <param name="dataContext">Data context model.</param> /// <param name="func">Function schema.</param> /// <param name="defaultSchemas">List of default database schema names.</param> private void BuildAggregateFunction(DataContextModel dataContext, AggregateFunction func, ISet <string> defaultSchemas) { var(name, isNonDefaultSchema) = ProcessObjectName(func.Name, defaultSchemas); var method = new MethodModel( _namingServices.NormalizeIdentifier(_options.DataModel.ProcedureNameOptions, (func.Name.Package != null ? $"{func.Name.Package}_" : null) + name.Name)) { Modifiers = Modifiers.Public | Modifiers.Static | Modifiers.Extension, Summary = func.Description, }; var metadata = new FunctionMetadata() { Name = name, ServerSideOnly = true, IsAggregate = true }; if (func.Parameters.Count > 0) { metadata.ArgIndices = new int[func.Parameters.Count]; for (var i = 0; i < metadata.ArgIndices.Length; i++) { metadata.ArgIndices[i] = i + 1; } } // just a guard if (func.Result is not ScalarResult scalarResult) { throw new InvalidOperationException($"Aggregate function {func.Name} returns non-scalar value."); } var typeMapping = MapType(scalarResult.Type); var funcModel = new AggregateFunctionModel(name, method, metadata, typeMapping.CLRType.WithNullability(scalarResult.Nullable)); BuildParameters(func.Parameters, funcModel.Parameters); _interceptors.PreprocessAggregateFunction(_languageProvider.TypeParser, funcModel); if (isNonDefaultSchema && _options.DataModel.GenerateSchemaAsType) { GetOrAddAdditionalSchema(dataContext, func.Name.Schema !).AggregateFunctions.Add(funcModel); } else { dataContext.AggregateFunctions.Add(funcModel); } }
/// <summary> /// Generates table function model from schema data. /// </summary> /// <param name="dataContext">Data context model.</param> /// <param name="func">Function schema.</param> /// <param name="defaultSchemas">List of default database schema names.</param> private void BuildTableFunction(DataContextModel dataContext, TableFunction func, ISet <string> defaultSchemas) { var(name, isNonDefaultSchema) = ProcessObjectName(func.Name, defaultSchemas); var method = new MethodModel( _namingServices.NormalizeIdentifier(_options.DataModel.ProcedureNameOptions, (func.Name.Package != null ? $"{func.Name.Package}_" : null) + name.Name)) { Modifiers = Modifiers.Public, Summary = func.Description }; var metadata = new TableFunctionMetadata() { Name = name }; var funcModel = new TableFunctionModel( name, method, metadata, _namingServices.NormalizeIdentifier(_options.DataModel.TableFunctionMethodInfoFieldNameOptions, func.Name.Name)) { Error = func.SchemaError?.Message }; BuildParameters(func.Parameters, funcModel.Parameters); if (func.Result != null) { funcModel.Result = PrepareResultSetModel(func.Name, func.Result); } _interceptors.PreprocessTableFunction(_languageProvider.TypeParser, funcModel); if (isNonDefaultSchema && _options.DataModel.GenerateSchemaAsType) { GetOrAddAdditionalSchema(dataContext, func.Name.Schema !).TableFunctions.Add(funcModel); } else { dataContext.TableFunctions.Add(funcModel); } }
public SchemaDetailForm(DataContextModel context, SchemaModel schema) { InitializeComponent(); this.schema = schema; this.context = context; dataTypeEdit.Items.AddRange(standardColumnTypes); dataTypeEdit.DropDownRows = standardColumnTypes.Length; columnPickerEdit.DataSource = schema.Columns; schemaBindingSource.DataSource = schema; schemaVGrid.DataSource = new[] { schema }; UpdateNames(); UpdateForeignSchemaEdit(true); UpdateColumnSqlNameEdit(); schema.PropertyChanged += schema_PropertyChanged; context.Schemas.ListChanged += Schemas_ListChanged; columnsView.BestFitColumns(); columnPickerEdit.View.BestFitColumns(); }
/// <summary> /// Creates empty data context class model for current schema and initialize it with basic properties. /// </summary> /// <returns>Data context model instance.</returns> private DataContextModel BuildDataContext() { string className; if (_options.DataModel.ContextClassName != null) { // name provided by user and shouldn't be modified except cases when it is invalid className = _namingServices.NormalizeIdentifier( NormalizationOptions.None, _options.DataModel.ContextClassName); } else { className = _namingServices.NormalizeIdentifier( _options.DataModel.DataContextClassNameOptions, _schemaProvider.DatabaseName != null ? (_schemaProvider.DatabaseName + "DB") : "MyDataContext"); } var dataContextClass = new ClassModel(className, className) { Modifiers = Modifiers.Public | Modifiers.Partial, Namespace = _options.CodeGeneration.Namespace }; if (_options.DataModel.BaseContextClass != null) { dataContextClass.BaseType = _languageProvider.TypeParser.Parse(_options.DataModel.BaseContextClass, false); } else { dataContextClass.BaseType = WellKnownTypes.LinqToDB.Data.DataConnection; } if (_options.DataModel.IncludeDatabaseInfo) { var summary = new StringBuilder(); if (_schemaProvider.DatabaseName != null) { summary.AppendFormat("Database : {0}", _schemaProvider.DatabaseName).AppendLine(); } if (_schemaProvider.DataSource != null) { summary.AppendFormat("Data Source : {0}", _schemaProvider.DataSource).AppendLine(); } if (_schemaProvider.ServerVersion != null) { summary.AppendFormat("Server Version : {0}", _schemaProvider.ServerVersion).AppendLine(); } if (summary.Length > 0) { dataContextClass.Summary = summary.ToString(); } } var dataContext = new DataContextModel(dataContextClass); dataContext.HasDefaultConstructor = _options.DataModel.HasDefaultConstructor; dataContext.HasConfigurationConstructor = _options.DataModel.HasConfigurationConstructor; dataContext.HasUntypedOptionsConstructor = _options.DataModel.HasUntypedOptionsConstructor; dataContext.HasTypedOptionsConstructor = _options.DataModel.HasTypedOptionsConstructor; return(dataContext); }
/// <summary> /// Generates scalar function model from schema data. /// </summary> /// <param name="dataContext">Data context model.</param> /// <param name="func">Function schema.</param> /// <param name="defaultSchemas">List of default database schema names.</param> private void BuildScalarFunction(DataContextModel dataContext, ScalarFunction func, ISet <string> defaultSchemas) { var(name, isNonDefaultSchema) = ProcessObjectName(func.Name, defaultSchemas); var method = new MethodModel( _namingServices.NormalizeIdentifier(_options.DataModel.ProcedureNameOptions, (func.Name.Package != null ? $"{func.Name.Package}_" : null) + name.Name)) { Modifiers = Modifiers.Public | Modifiers.Static, Summary = func.Description }; var metadata = new FunctionMetadata() { Name = name, ServerSideOnly = true }; var funcModel = new ScalarFunctionModel(name, method, metadata); BuildParameters(func.Parameters, funcModel.Parameters); // thanks to pgsql, scalar function could return not only scalar, but also tuple or just nothing switch (func.Result.Kind) { case ResultKind.Scalar: { var scalarResult = (ScalarResult)func.Result; var typeMapping = MapType(scalarResult.Type); funcModel.Return = typeMapping.CLRType.WithNullability(scalarResult.Nullable); // TODO: DataType not used by current scalar function mapping API break; } case ResultKind.Tuple: { var tupleResult = (TupleResult)func.Result; // tuple model class var @class = new ClassModel( _namingServices.NormalizeIdentifier( _options.DataModel.FunctionTupleResultClassNameOptions, func.Name.Name)) { Modifiers = Modifiers.Public | Modifiers.Partial }; funcModel.ReturnTuple = new TupleModel(@class) { CanBeNull = tupleResult.Nullable }; // fields order must be preserved, as tuple fields mapped by ordinal foreach (var field in tupleResult.Fields) { var typeMapping = MapType(field.Type); var prop = new PropertyModel(_namingServices.NormalizeIdentifier(_options.DataModel.FunctionTupleResultPropertyNameOptions, field.Name ?? "Field"), typeMapping.CLRType.WithNullability(field.Nullable)) { Modifiers = Modifiers.Public, IsDefault = true, HasSetter = true }; funcModel.ReturnTuple.Fields.Add(new TupleFieldModel(prop, field.Type) { DataType = typeMapping.DataType }); } break; } case ResultKind.Void: // just regular postgresql void function, nothing to see here... // because function must have return type to be callable in query, we set return type to object? funcModel.Return = WellKnownTypes.System.ObjectNullable; break; } _interceptors.PreprocessScalarFunction(_languageProvider.TypeParser, funcModel); if (isNonDefaultSchema && _options.DataModel.GenerateSchemaAsType) { GetOrAddAdditionalSchema(dataContext, func.Name.Schema !).ScalarFunctions.Add(funcModel); } else { dataContext.ScalarFunctions.Add(funcModel); } }
/// <summary> /// Generates stored procedure model from schema data. /// </summary> /// <param name="dataContext">Data context model.</param> /// <param name="func">Function schema.</param> /// <param name="defaultSchemas">List of default database schema names.</param> private void BuildStoredProcedure(DataContextModel dataContext, StoredProcedure func, ISet <string> defaultSchemas) { var(name, isNonDefaultSchema) = ProcessObjectName(func.Name, defaultSchemas); var method = new MethodModel( _namingServices.NormalizeIdentifier(_options.DataModel.ProcedureNameOptions, (func.Name.Package != null ? $"{func.Name.Package}_" : null) + name.Name)) { Modifiers = Modifiers.Public | Modifiers.Static | Modifiers.Extension, Summary = func.Description, }; var funcModel = new StoredProcedureModel(name, method) { Error = func.SchemaError?.Message }; BuildParameters(func.Parameters, funcModel.Parameters); switch (func.Result.Kind) { case ResultKind.Void: break; case ResultKind.Tuple: // no support from db (maybe pgsql could do it?) and schema API now throw new NotImplementedException($"Tuple return type support not implemented for stored procedures"); case ResultKind.Scalar: { var scalarResult = (ScalarResult)func.Result; var typeMapping = MapType(scalarResult.Type); var paramName = _namingServices.NormalizeIdentifier(_options.DataModel.ProcedureParameterNameOptions, scalarResult.Name ?? "return"); funcModel.Return = new FunctionParameterModel( new ParameterModel(paramName, typeMapping.CLRType.WithNullability(scalarResult.Nullable), CodeParameterDirection.Out), System.Data.ParameterDirection.ReturnValue) { Type = scalarResult.Type, DataType = typeMapping.DataType, DbName = scalarResult.Name, IsNullable = scalarResult.Nullable }; break; } } FunctionResult?resultModel = null; if (func.ResultSets?.Count > 1) { // TODO: to support multi-result sets we need at least one implementation in schema provider throw new NotImplementedException($"Multi-set stored procedures not supported"); } else if (func.ResultSets?.Count == 1) { funcModel.Results.Add(resultModel = PrepareResultSetModel(func.Name, func.ResultSets[0])); } // prepare async result class descriptor if needed var returningParameters = funcModel.Parameters.Where(p => p.Direction != System.Data.ParameterDirection.Input).ToList(); if (funcModel.Return != null) { returningParameters.Add(funcModel.Return); } if (returningParameters.Count > 0) { var asyncResult = new AsyncProcedureResult( new ClassModel( _namingServices.NormalizeIdentifier( _options.DataModel.AsyncProcedureResultClassNameOptions, func.Name.Name)) { Modifiers = Modifiers.Public }, new PropertyModel("Result") { Modifiers = Modifiers.Public, IsDefault = true, HasSetter = true }); foreach (var parameter in returningParameters) { asyncResult.ParameterProperties.Add( parameter, new PropertyModel(_namingServices.NormalizeIdentifier(_options.DataModel.AsyncProcedureResultClassPropertiesNameOptions, parameter.Parameter.Name), parameter.Parameter.Type) { Modifiers = Modifiers.Public, IsDefault = true, HasSetter = true }); } // TODO: next line will need refactoring if we add multi-set support funcModel.Results.Clear(); funcModel.Results.Add(new FunctionResult(resultModel?.CustomTable, resultModel?.Entity, asyncResult)); } _interceptors.PreprocessStoredProcedure(_languageProvider.TypeParser, funcModel); if (isNonDefaultSchema && _options.DataModel.GenerateSchemaAsType) { GetOrAddAdditionalSchema(dataContext, func.Name.Schema !).StoredProcedures.Add(funcModel); } else { dataContext.StoredProcedures.Add(funcModel); } }
public static IEnumerable <SchemaModel> ReadSchemas(DataContextModel owner, DBConnector database) { if (database == null) { throw new ArgumentNullException("database"); } List <SchemaModel> tables = new List <SchemaModel>(); List <Action> postColumnActions = new List <Action>(); Func <string, string, SchemaModel> Table = (schema, name) => //First look for a new table from SQL Server, then for an existing one. String.IsNullOrEmpty(name) ? null : tables.SingleOrDefault(t => t.SqlSchemaName == schema && t.SqlName == name) ?? owner.Schemas.SingleOrDefault(t => t.SqlSchemaName == schema && t.SqlName == name); using (var connection = database.OpenConnection()) { #region Read Tables using (var reader = database.ExecuteReader(TablesSql)) { while (reader.Read()) { var table = new SchemaModel(owner) { Name = (string)reader["TableName"], SqlName = (string)reader["TableName"], SqlSchemaName = reader["SchemaName"] as string }; if (Table(table.SqlSchemaName, table.SqlName) != null) { continue; //TODO: Import column } string keyName = reader["PrimaryKeyName"] as string; if (!String.IsNullOrEmpty(keyName)) { postColumnActions.Add( () => table.PrimaryKey = table.Columns.Single(c => c.SqlName == keyName) ); } tables.Add(table); } } #endregion using (var reader = database.ExecuteReader(ColumnsSql)) { while (reader.Read()) { var table = Table((string)reader["SchemaName"], (string)reader["TableName"]); if (table == null) { continue; //Skip tables without RowVersion columns } var name = (string)reader["ColumnName"]; if (table.Columns.Any(c => c.SqlName == name)) { continue; //Don't add duplicate columns to existing tables. } table.Columns.Add(new ColumnModel(table) { Name = name, SqlName = name, DataType = SqlTypes[(string)reader["DataType"]], AllowNulls = 1 == (int)reader["AllowNulls"], IsUnique = 1 == (int)reader["IsUnique"], ForeignSchema = Table(reader["ForeignSchema"] as string, reader["ForeignTable"] as string) }); } } } postColumnActions.ForEach(a => a()); return(tables); }
/// <summary> /// Creates entity model from table/view schema information. /// </summary> /// <param name="dataContext">Current data model's data context descriptor.</param> /// <param name="table">Table or view schema data.</param> /// <param name="defaultSchemas">List of default database schema names.</param> /// <param name="baseType">Optional base entity class type.</param> private void BuildEntity( DataContextModel dataContext, TableLikeObject table, ISet <string> defaultSchemas, IType?baseType) { var(tableName, isNonDefaultSchema) = ProcessObjectName(table.Name, defaultSchemas); var metadata = new EntityMetadata() { Name = tableName, IsView = table is View }; // generate name for entity table property in data context class var contextPropertyName = _options.DataModel.EntityContextPropertyNameProvider?.Invoke(table); contextPropertyName = contextPropertyName != null ? contextPropertyName : _namingServices.NormalizeIdentifier( _options.DataModel.EntityContextPropertyNameOptions, table.Name.Name); // generate entity class name var className = _options.DataModel.EntityClassNameProvider?.Invoke(table); var hasCustomClassName = className != null; className = className != null ? className : _namingServices.NormalizeIdentifier( _options.DataModel.EntityClassNameOptions, table.Name.Name); // add schema name ato entity class name as prefix for table from non-default schema without // class-per-schema option set if (!hasCustomClassName && !_options.DataModel.GenerateSchemaAsType && isNonDefaultSchema) { className = table.Name.Schema + "_" + className; } // entity class properties var classModel = new ClassModel(_options.CodeGeneration.ClassPerFile ? className : dataContext.Class.FileName !, className); classModel.Summary = table.Description; classModel.BaseType = baseType; classModel.Namespace = _options.CodeGeneration.Namespace; classModel.Modifiers = Modifiers.Public; if (_options.DataModel.EntityClassIsPartial) { classModel.Modifiers = classModel.Modifiers | Modifiers.Partial; } // entity data model var entity = new EntityModel( metadata, classModel, contextPropertyName == null ? null // note that property type is open-generic here // concrete type argument will be set later during AST generation : new PropertyModel(contextPropertyName, WellKnownTypes.LinqToDB.ITableT) { Modifiers = Modifiers.Public, Summary = table.Description }); entity.ImplementsIEquatable = _options.DataModel.GenerateIEquatable; entity.FindExtensions = _options.DataModel.GenerateFindExtensions; // add entity to lookup _entities.Add(table.Name, new TableWithEntity(table, entity)); BuildEntityColumns(table, entity); // call interceptor after entity model completely configured _interceptors.PreprocessEntity(_languageProvider.TypeParser, entity); // add entity to model if (isNonDefaultSchema && _options.DataModel.GenerateSchemaAsType) { GetOrAddAdditionalSchema(dataContext, table.Name.Schema !).Entities.Add(entity); } else { dataContext.Entities.Add(entity); } }