Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented scaffolding/reverse engineer from database.
- Loading branch information
Showing
10 changed files
with
310 additions
and
5 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
src/EFCore.OpenEdge/Design/Internal/OpenEdgeAnnotationCodeGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using Microsoft.EntityFrameworkCore.Design; | ||
|
||
namespace EntityFrameworkCore.OpenEdge.Design.Internal | ||
{ | ||
public class OpenEdgeAnnotationCodeGenerator : AnnotationCodeGenerator | ||
{ | ||
public OpenEdgeAnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) : base(dependencies) | ||
{ | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
src/EFCore.OpenEdge/Design/Internal/OpenEdgeDesignTimeServices.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using EntityFrameworkCore.OpenEdge.Scaffolding.Internal; | ||
using EntityFrameworkCore.OpenEdge.Storage.Internal.Mapping; | ||
using Microsoft.EntityFrameworkCore.Design; | ||
using Microsoft.EntityFrameworkCore.Scaffolding; | ||
using Microsoft.EntityFrameworkCore.Storage; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace EntityFrameworkCore.OpenEdge.Design.Internal | ||
{ | ||
public class OpenEdgeDesignTimeServices : IDesignTimeServices | ||
{ | ||
public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) | ||
=> serviceCollection | ||
.AddSingleton<IRelationalTypeMappingSource, OpenEdgeTypeMappingSource>() | ||
.AddSingleton<IDatabaseModelFactory, OpenEdgeDatabaseModelFactory>() | ||
.AddSingleton<IProviderConfigurationCodeGenerator, OpenEdgeCodeGenerator>() | ||
.AddSingleton<IAnnotationCodeGenerator, OpenEdgeAnnotationCodeGenerator>() | ||
; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
src/EFCore.OpenEdge/Extensions/OpenEdgeDataReaderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System; | ||
using System.Data.Common; | ||
|
||
namespace EntityFrameworkCore.OpenEdge.Extensions | ||
{ | ||
public static class OpenEdgeDataReaderExtensions | ||
{ | ||
public static T GetValueOrDefault<T>(this DbDataReader reader, string name) | ||
{ | ||
var idx = reader.GetOrdinal(name); | ||
return reader.IsDBNull(idx) | ||
? default | ||
: (T)GetValue<T>(reader.GetValue(idx)); | ||
} | ||
|
||
public static T GetValueOrDefault<T>(this DbDataRecord record, string name) | ||
{ | ||
var idx = record.GetOrdinal(name); | ||
return record.IsDBNull(idx) | ||
? default | ||
: (T)GetValue<T>(record.GetValue(idx)); | ||
} | ||
|
||
private static object GetValue<T>(object valueRecord) | ||
{ | ||
switch (typeof(T).Name) | ||
{ | ||
case nameof(Int32): | ||
return Convert.ToInt32(valueRecord); | ||
case nameof(Boolean): | ||
return Convert.ToBoolean(valueRecord); | ||
default: | ||
return valueRecord; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
using Microsoft.EntityFrameworkCore.Design; | ||
|
||
[assembly: DesignTimeProviderServices("EntityFrameworkCore.OpenEdge.Design.Internal.OpenEdgeDesignTimeServices")] |
20 changes: 20 additions & 0 deletions
20
src/EFCore.OpenEdge/Scaffolding/Internal/OpenEdgeCodeGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using EntityFrameworkCore.OpenEdge.Extensions; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Design; | ||
using Microsoft.EntityFrameworkCore.Scaffolding; | ||
|
||
namespace EntityFrameworkCore.OpenEdge.Scaffolding.Internal | ||
{ | ||
public class OpenEdgeCodeGenerator : ProviderCodeGenerator | ||
{ | ||
public OpenEdgeCodeGenerator(ProviderCodeGeneratorDependencies dependencies) | ||
: base(dependencies) | ||
{ | ||
} | ||
|
||
public override MethodCallCodeFragment GenerateUseProvider(string connectionString) | ||
{ | ||
return new MethodCallCodeFragment(nameof(OpenEdgeDbContextOptionsBuilderExtensions.UseOpenEdge), connectionString); | ||
} | ||
} | ||
} |
205 changes: 205 additions & 0 deletions
205
src/EFCore.OpenEdge/Scaffolding/Internal/OpenEdgeDatabaseModelFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Data; | ||
using System.Data.Common; | ||
using System.Data.Odbc; | ||
using System.Linq; | ||
using System.Text; | ||
using EntityFrameworkCore.OpenEdge.Extensions; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Diagnostics; | ||
using Microsoft.EntityFrameworkCore.Internal; | ||
using Microsoft.EntityFrameworkCore.Metadata; | ||
using Microsoft.EntityFrameworkCore.Migrations; | ||
using Microsoft.EntityFrameworkCore.Scaffolding; | ||
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; | ||
|
||
namespace EntityFrameworkCore.OpenEdge.Scaffolding.Internal | ||
{ | ||
public class OpenEdgeDatabaseModelFactory : IDatabaseModelFactory | ||
{ | ||
protected internal const string DatabaseModelDefaultSchema = "pub"; | ||
private readonly IDiagnosticsLogger<DbLoggerCategory.Scaffolding> _logger; | ||
|
||
public OpenEdgeDatabaseModelFactory(IDiagnosticsLogger<DbLoggerCategory.Scaffolding> logger) | ||
{ | ||
_logger = logger; | ||
} | ||
|
||
public DatabaseModel Create(string connectionString, IEnumerable<string> tables, IEnumerable<string> schemas) | ||
{ | ||
using (var connection = new OdbcConnection(connectionString)) | ||
{ | ||
return Create(connection, tables, schemas); | ||
} | ||
} | ||
|
||
public DatabaseModel Create(DbConnection connection, IEnumerable<string> tables, IEnumerable<string> schemas) | ||
{ | ||
var databaseModel = new DatabaseModel(); | ||
|
||
var connectionStartedOpen = connection.State == ConnectionState.Open; | ||
if (!connectionStartedOpen) | ||
{ | ||
connection.Open(); | ||
} | ||
|
||
try | ||
{ | ||
databaseModel.DefaultSchema = DatabaseModelDefaultSchema; | ||
|
||
GetTables(connection, null, databaseModel); | ||
|
||
return databaseModel; | ||
} | ||
finally | ||
{ | ||
if (!connectionStartedOpen) | ||
{ | ||
connection.Close(); | ||
} | ||
} | ||
} | ||
|
||
private void GetTables( | ||
DbConnection connection, | ||
Func<string, string> tableFilter, | ||
DatabaseModel databaseModel) | ||
{ | ||
using (var command = connection.CreateCommand()) | ||
{ | ||
var commandText = @" | ||
SELECT | ||
t.""_File-Name"" AS 'name' | ||
FROM ""pub"".""_File"" t "; | ||
|
||
var filter = | ||
$"WHERE t.\"_File-Name\" <> '{HistoryRepository.DefaultTableName}' {(tableFilter != null ? $" AND {tableFilter("t.\"_file-name\"")}" : "")}"; | ||
|
||
Console.WriteLine(commandText + filter); | ||
|
||
command.CommandText = commandText + filter; | ||
|
||
using (var reader = command.ExecuteReader()) | ||
{ | ||
while (reader.Read()) | ||
{ | ||
var name = reader.GetValueOrDefault<string>("name"); | ||
|
||
var table = new DatabaseTable | ||
{ | ||
Schema = DatabaseModelDefaultSchema, | ||
Name = name | ||
}; | ||
|
||
databaseModel.Tables.Add(table); | ||
} | ||
} | ||
|
||
GetColumns(connection, filter, databaseModel); | ||
} | ||
} | ||
|
||
private void GetColumns( | ||
DbConnection connection, | ||
string tableFilter, | ||
DatabaseModel databaseModel) | ||
{ | ||
using (var command = connection.CreateCommand()) | ||
{ | ||
command.CommandText = new StringBuilder() | ||
.AppendLine("SELECT") | ||
.AppendLine(" f.\"_Field-Name\",") | ||
.AppendLine(" f.\"_Data-Type\",") | ||
.AppendLine(" t.\"_File-Name\" as name,") | ||
.AppendLine(" t.\"_Prime-Index\" as primeindex,") | ||
.AppendLine(" f.\"_Mandatory\",") | ||
.AppendLine(" if.\"_index-recid\" as identity,") | ||
.AppendLine(" f.\"_initial\"") | ||
.AppendLine("FROM pub.\"_field\" f") | ||
.AppendLine("INNER JOIN pub.\"_File\" t ") | ||
.AppendLine("ON t.rowid = f.\"_File-recid\"") | ||
.AppendLine("LEFT JOIN pub.\"_index-field\" if ") | ||
.AppendLine("ON if.\"_index-recid\" = t.\"_Prime-Index\" AND if.\"_field-recid\" = f.rowid") | ||
.AppendLine(tableFilter) | ||
.AppendLine("ORDER BY f.\"_Order\"") | ||
.ToString(); | ||
|
||
using (var reader = command.ExecuteReader()) | ||
{ | ||
var tableColumnGroups = reader.Cast<DbDataRecord>() | ||
.GroupBy( | ||
ddr => ddr.GetValueOrDefault<string>("name")); | ||
|
||
foreach (var tableColumnGroup in tableColumnGroups) | ||
{ | ||
var tableName = tableColumnGroup.Key; | ||
var table = databaseModel.Tables.Single(t => t.Schema == DatabaseModelDefaultSchema && t.Name == tableName); | ||
|
||
var primaryKey = new DatabasePrimaryKey | ||
{ | ||
Table = table, | ||
Name = table + "_PK" | ||
}; | ||
|
||
table.PrimaryKey = primaryKey; | ||
|
||
foreach (var dataRecord in tableColumnGroup) | ||
{ | ||
var columnName = dataRecord.GetValueOrDefault<string>("_Field-Name"); | ||
var dataTypeName = dataRecord.GetValueOrDefault<string>("_Data-Type"); | ||
var isNullable = !dataRecord.GetValueOrDefault<bool>("_Mandatory"); | ||
var isIdentity = dataRecord.GetValueOrDefault<long?>("identity") != null; | ||
var defaultValue = !isIdentity ? dataRecord.GetValueOrDefault<object>("_initial") : null; | ||
|
||
var storeType = dataTypeName; | ||
if (string.IsNullOrWhiteSpace(defaultValue?.ToString())) | ||
{ | ||
defaultValue = null; | ||
} | ||
|
||
string mapStoreType(string value) | ||
{ | ||
switch (value) | ||
{ | ||
|
||
default: | ||
return value; | ||
} | ||
} | ||
|
||
table.Columns.Add(new DatabaseColumn | ||
{ | ||
Table = table, | ||
Name = columnName, | ||
StoreType = mapStoreType(storeType), | ||
IsNullable = isNullable, | ||
DefaultValueSql = defaultValue?.ToString(), | ||
ValueGenerated = isIdentity | ||
? ValueGenerated.OnAdd | ||
: default(ValueGenerated?) | ||
}); | ||
|
||
|
||
if (isIdentity) | ||
{ | ||
var column = table.Columns.FirstOrDefault(c => c.Name == columnName) | ||
?? table.Columns.FirstOrDefault(c => c.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase)); | ||
primaryKey.Columns.Add(column); | ||
} | ||
} | ||
|
||
if (!primaryKey.Columns.Any()) | ||
{ | ||
databaseModel.Tables.Remove(table); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
private static string DisplayName(string schema, string name) | ||
=> (!string.IsNullOrEmpty(schema) ? schema + "." : "") + name; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters