Skip to content

Commit

Permalink
Implemented scaffolding/reverse engineer from database.
Browse files Browse the repository at this point in the history
  • Loading branch information
alexwiese committed Dec 3, 2018
1 parent d79aabe commit 9dac410
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 5 deletions.
@@ -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 src/EFCore.OpenEdge/Design/Internal/OpenEdgeDesignTimeServices.cs
@@ -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>()
;
}
}
2 changes: 2 additions & 0 deletions src/EFCore.OpenEdge/EFCore.OpenEdge.csproj
Expand Up @@ -21,6 +21,8 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.1</Version>
<PackageReleaseNotes>Implemented reverse engineer/scaffold from database</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
Expand Down
37 changes: 37 additions & 0 deletions src/EFCore.OpenEdge/Extensions/OpenEdgeDataReaderExtensions.cs
@@ -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;
}
}
}
}
Expand Up @@ -3,7 +3,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace EntityFrameworkCore.OpenEdge.Extensions
namespace Microsoft.EntityFrameworkCore
{
public static class OpenEdgeDbContextOptionsBuilderExtensions
{
Expand Down
Expand Up @@ -24,7 +24,7 @@ public static IServiceCollection AddEntityFrameworkOpenEdge(this IServiceCollect
{
var builder = new EntityFrameworkRelationalServicesBuilder(serviceCollection)
.TryAdd<IDatabaseProvider, DatabaseProvider<OpenEdgeOptionsExtension>>()
.TryAdd<IRelationalTypeMappingSource, OpenEdgeRelationalTypeMappingSource>()
.TryAdd<IRelationalTypeMappingSource, OpenEdgeTypeMappingSource>()
.TryAdd<ISqlGenerationHelper, OpenEdgeSqlGenerationHelper>()
.TryAdd<IConventionSetBuilder, OpenEdgeRelationalConventionSetBuilder>()
.TryAdd<IUpdateSqlGenerator, OpenEdgeUpdateSqlGenerator>()
Expand Down
3 changes: 3 additions & 0 deletions src/EFCore.OpenEdge/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
using Microsoft.EntityFrameworkCore.Design;

[assembly: DesignTimeProviderServices("EntityFrameworkCore.OpenEdge.Design.Internal.OpenEdgeDesignTimeServices")]
20 changes: 20 additions & 0 deletions src/EFCore.OpenEdge/Scaffolding/Internal/OpenEdgeCodeGenerator.cs
@@ -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);
}
}
}
@@ -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;
}
}
Expand Up @@ -5,11 +5,12 @@

namespace EntityFrameworkCore.OpenEdge.Storage.Internal.Mapping
{
public class OpenEdgeRelationalTypeMappingSource : RelationalTypeMappingSource
public class OpenEdgeTypeMappingSource : RelationalTypeMappingSource
{
private const int VarcharMaxSize = 32000;

private readonly DateTimeTypeMapping _datetime = new DateTimeTypeMapping("datetime", DbType.DateTime);
private readonly DateTimeOffsetTypeMapping _datetimeOffset = new DateTimeOffsetTypeMapping("datetime-tz", DbType.DateTimeOffset);
private readonly DateTimeTypeMapping _date = new DateTimeTypeMapping("date", DbType.Date);
private readonly DateTimeTypeMapping _timeStamp = new DateTimeTypeMapping("timestamp", DbType.DateTime);
private readonly TimeSpanTypeMapping _time = new TimeSpanTypeMapping("time", DbType.Time);
Expand All @@ -32,7 +33,7 @@ public class OpenEdgeRelationalTypeMappingSource : RelationalTypeMappingSource
private readonly Dictionary<string, RelationalTypeMapping> _storeTypeMappings;
private readonly Dictionary<Type, RelationalTypeMapping> _clrTypeMappings;

public OpenEdgeRelationalTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies)
public OpenEdgeTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies)
: base(dependencies, relationalDependencies)
{
_clrTypeMappings
Expand All @@ -56,28 +57,34 @@ public OpenEdgeRelationalTypeMappingSource(TypeMappingSourceDependencies depende
= new Dictionary<string, RelationalTypeMapping>(StringComparer.OrdinalIgnoreCase)
{
{ "bigint", _bigint },
{ "int64", _bigint },
{ "binary varying", _binary },
{ "raw", _binary },
{ "binary", _binary },
{ "bit", _boolean},
{ "logical", _boolean},
{ "char varying", _char },
{ "char", _char },
{ "character varying", _char },
{ "character", _char },
{ "date", _date },
{ "datetime", _datetime },
{ "datetime2", _datetime },
{ "datetimeoffset", _datetime },
{ "datetimeoffset", _datetimeOffset },
{ "datetime-tz", _datetimeOffset },
{ "dec", _decimal },
{ "decimal", _decimal },
{ "double precision", _double },
{ "float", _double },
{ "image", _binary },
{ "int", _integer },
{ "integer", _integer },
{ "money", _decimal },
{ "numeric", _decimal },
{ "real", _float },
{ "smalldatetime", _datetime },
{ "smallint", _smallint},
{ "short", _smallint},
{ "smallmoney", _decimal },
{ "text", _char},
{ "time", _time },
Expand Down

0 comments on commit 9dac410

Please sign in to comment.