From 0f86552ec99c7bfc217533b92bfb3d34640f879c Mon Sep 17 00:00:00 2001 From: Xavier Jefferson Date: Fri, 15 Dec 2023 10:51:56 -0800 Subject: [PATCH 1/2] Add MSAccess db type. --- NMG.App/ConnectionDialog.cs | 2 + NMG.Core/DataTypeMapper.cs | 6 + NMG.Core/Domain/ServerType.cs | 3 +- NMG.Core/MetadataFactory.cs | 2 + NMG.Core/NMG.Core.csproj | 1 + NMG.Core/Reader/MSAccessMetadataReader.cs | 594 ++++++++++++++++++++++ NMG.Core/Util/StringConstants.cs | 2 + 7 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 NMG.Core/Reader/MSAccessMetadataReader.cs diff --git a/NMG.App/ConnectionDialog.cs b/NMG.App/ConnectionDialog.cs index 053550e..66f1d46 100644 --- a/NMG.App/ConnectionDialog.cs +++ b/NMG.App/ConnectionDialog.cs @@ -84,6 +84,8 @@ private string GetDefaultConnectionStringForServerType(ServerType serverType) { switch (serverType) { + case ServerType.MSAccess: + return StringConstants.MSACCESS_CONN_STR_TEMPLATE; case ServerType.Oracle: return StringConstants.ORACLE_CONN_STR_TEMPLATE; case ServerType.SqlServer: diff --git a/NMG.Core/DataTypeMapper.cs b/NMG.Core/DataTypeMapper.cs index 38e84cd..aea01d2 100644 --- a/NMG.Core/DataTypeMapper.cs +++ b/NMG.Core/DataTypeMapper.cs @@ -52,6 +52,8 @@ public Type MapFromDBType(ServerType serverType, string dataType, int? dataLengt return MapFromOracleDBType(dataType, dataLength, dataPrecision, dataScale); case ServerType.MySQL: return MapFromMySqlDBType(dataType, dataLength, dataPrecision, dataScale); + case ServerType.MSAccess: + return MapFromMSAccessDbType(dataType, dataLength, dataPrecision, dataScale); case ServerType.SQLite: return MapFromSqliteDbType(dataType, dataLength, dataPrecision, dataScale); case ServerType.PostgreSQL: @@ -167,6 +169,10 @@ private Type MapFromSqliteDbType(string dataType, int? dataLength, int? dataPrec { return MapFromDBType(dataType, dataLength, dataPrecision, dataScale); } + private Type MapFromMSAccessDbType(string dataType, int? dataLength, int? dataPrecision, int? dataScale) + { + return MapFromDBType(dataType, dataLength, dataPrecision, dataScale); + } private Type MapFromInformixDbType(string dataType, int? dataLength, int? dataPrecision, int? dataScale) { diff --git a/NMG.Core/Domain/ServerType.cs b/NMG.Core/Domain/ServerType.cs index 72b240a..b5e3cdb 100644 --- a/NMG.Core/Domain/ServerType.cs +++ b/NMG.Core/Domain/ServerType.cs @@ -10,6 +10,7 @@ public enum ServerType Sybase, Informix, Ingres, - CUBRID + CUBRID, + MSAccess } } \ No newline at end of file diff --git a/NMG.Core/MetadataFactory.cs b/NMG.Core/MetadataFactory.cs index 30d29dd..fcfc29d 100644 --- a/NMG.Core/MetadataFactory.cs +++ b/NMG.Core/MetadataFactory.cs @@ -27,6 +27,8 @@ public static IMetadataReader GetReader(ServerType serverType, string connection return new IngresMetadataReader(connectionStr); case ServerType.CUBRID: return new CUBRIDMetadataReader(connectionStr); + case ServerType.MSAccess: + return new MSAccessMetadataReader(connectionStr); default: return new NpgsqlMetadataReader(connectionStr); } diff --git a/NMG.Core/NMG.Core.csproj b/NMG.Core/NMG.Core.csproj index 091d8aa..39ed73b 100644 --- a/NMG.Core/NMG.Core.csproj +++ b/NMG.Core/NMG.Core.csproj @@ -162,6 +162,7 @@ + diff --git a/NMG.Core/Reader/MSAccessMetadataReader.cs b/NMG.Core/Reader/MSAccessMetadataReader.cs new file mode 100644 index 0000000..30e54c6 --- /dev/null +++ b/NMG.Core/Reader/MSAccessMetadataReader.cs @@ -0,0 +1,594 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.OleDb; +using System.Linq; +using NMG.Core.Domain; + +namespace NMG.Core.Reader +{ + public class MSAccessMetadataReader : IMetadataReader + { + [Flags] + public enum OleDbColumnFlagsEnum : long + { + ISBOOKMARK = 0x1, + + MAYDEFER = 0x2, + + WRITE = 0x4, + + WRITEUNKNOWN = 0x8, + + ISFIXEDLENGTH = 0x10, + + ISNULLABLE = 0x20, + + MAYBENULL = 0x40, + + ISLONG = 0x80, + + ISROWID = 0x100, + + ISROWVER = 0x200, + + CACHEDEFERRED = 0x1000 + } + + /// + /// Reference at + /// https://stackoverflow.com/questions/21388962/columns-flags-field-values-for-columns-collection-of-ole-db-schema-collections + /// + private static readonly OleDbColumnFlagsEnum AutonumberColumnFlags = OleDbColumnFlagsEnum.MAYBENULL | + OleDbColumnFlagsEnum.ISFIXEDLENGTH | + OleDbColumnFlagsEnum.WRITEUNKNOWN | + OleDbColumnFlagsEnum.MAYDEFER; + + private static readonly OleDbColumnFlagsEnum ShortTextFlags = OleDbColumnFlagsEnum.MAYBENULL | + OleDbColumnFlagsEnum.WRITEUNKNOWN | + OleDbColumnFlagsEnum.MAYDEFER; + + private static readonly OleDbColumnFlagsEnum LongTextFlags = + OleDbColumnFlagsEnum.ISLONG | OleDbColumnFlagsEnum.MAYBENULL | OleDbColumnFlagsEnum.ISNULLABLE | + OleDbColumnFlagsEnum.WRITEUNKNOWN | + OleDbColumnFlagsEnum.MAYDEFER; + + private static readonly OleDbColumnFlagsEnum PictureFlags = + OleDbColumnFlagsEnum.ISLONG | OleDbColumnFlagsEnum.MAYBENULL | OleDbColumnFlagsEnum.ISNULLABLE | + OleDbColumnFlagsEnum.WRITEUNKNOWN | + OleDbColumnFlagsEnum.MAYDEFER; + + private readonly string _connectionStr; + private readonly Lazy _lazySchemaInfo; + + public MSAccessMetadataReader(string connectionStr) + { + _connectionStr = connectionStr; + _lazySchemaInfo = new Lazy(GetSchemaInfo); + } + + private object _mutex = new object(); + public IList GetTableDetails(Table table, string owner) + { + lock (_mutex) + { + var info = _lazySchemaInfo.Value; + var accessColumns = info.Columns.Where(i => i.TABLE_NAME == table.Name).ToList(); + var accessIndexes = info.Indexes.Where(i => i.TABLE_NAME == table.Name).ToList(); + var constraintColumnUsage = + info.ConstraintColumnUsages.Where(i => i.TABLE_NAME == table.Name).ToList(); + var foreignKeys = info.ForeignKeys.Where(i => i.PK_TABLE_NAME == table.Name).ToList(); + var dataTypeMapper = new DataTypeMapper(); + var result = accessColumns + .Select(c => CreateColumn(c, accessIndexes, constraintColumnUsage, dataTypeMapper, info)).ToList(); + table.Owner = owner; + table.Columns = result; + table.PrimaryKey = DeterminePrimaryKeys(table); + table.HasManyRelationships = DetermineHasManyRelationships(table, foreignKeys); + table.ForeignKeys = DetermineForeignKeyReferences(table); + return result; + } + } + + public IList GetOwners() + { + return new List { "dbo" }; + } + + public List GetSequences(string owner) + { + return new List(); + } + + public PrimaryKey DeterminePrimaryKeys(Table table) + { + var primaryKeys = table.Columns.Where(x => x.IsPrimaryKey).ToList(); + + if (primaryKeys.Count() == 1) + { + var c = primaryKeys.First(); + var key = new PrimaryKey + { + Type = PrimaryKeyType.PrimaryKey, + Columns = { c } + }; + return key; + } + + if (primaryKeys.Count() > 1) + { + var key = new PrimaryKey + { + Type = PrimaryKeyType.CompositeKey, + Columns = primaryKeys + }; + return key; + } + + return null; + } + + public IList DetermineForeignKeyReferences(Table table) + { + var foreignKeys = table.Columns.Where(x => x.IsForeignKey).Distinct() + .Select(column => new ForeignKey + { + Name = column.Name, + UniquePropertyName = column.Name, + References = column.ForeignKeyTableName, + Columns = DetermineColumnsForForeignKey(table.Columns, column.ConstraintName) + }).ToList(); + + Table.SetUniqueNamesForForeignKeyProperties(foreignKeys); + + return foreignKeys; + } + + public List GetTables(string owner) + { + var info = _lazySchemaInfo.Value; + return info.Tables.Where(i => i.TABLE_TYPE == "TABLE" || i.TABLE_TYPE == "PASS-THROUGH") + .Select(i => new Table { Name = i.TABLE_NAME }).ToList(); + } + + /// + /// reference at + /// https://stackoverflow.com/questions/21388962/columns-flags-field-values-for-columns-collection-of-ole-db-schema-collections + /// + /// + /// + private bool IsAutonumber(OleDbColumn column) + { + return column.COLUMN_FLAGS == AutonumberColumnFlags && column.DATA_TYPE == OleDbType.Integer; + } + + private SchemaInfo GetSchemaInfo() + { + SchemaInfo info; + + using (var connection = new OleDbConnection(_connectionStr)) + { + try + { + connection.Open(); + var result = new SchemaInfo(); + + var restrictions = new object[] { null, null, null, null }; + var columnsTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, restrictions); + var tablesTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, restrictions); + var indexesTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Indexes, restrictions); + var keyColumnUsageTable = + connection.GetOleDbSchemaTable(OleDbSchemaGuid.Key_Column_Usage, restrictions); + var foreignKeysTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Foreign_Keys, restrictions); + var constraintUsageColumnTable = + connection.GetOleDbSchemaTable(OleDbSchemaGuid.Constraint_Column_Usage, restrictions); + var proceduresTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Procedures, restrictions); + var tableConstraintsTable = + connection.GetOleDbSchemaTable(OleDbSchemaGuid.Table_Constraints, restrictions); + result.Tables = tablesTable.Rows.Cast().Select(i => new OleDbTable + { + TABLE_CATALOG = ConvertString(i, "TABLE_CATALOG"), + TABLE_SCHEMA = ConvertString(i, "TABLE_SCHEMA"), + TABLE_NAME = ConvertString(i, "TABLE_NAME"), + TABLE_TYPE = ConvertString(i, "TABLE_TYPE"), + TABLE_GUID = ConvertStruct(i, "TABLE_GUID"), + DESCRIPTION = ConvertString(i, "DESCRIPTION"), + TABLE_PROPID = ConvertStruct(i, "TABLE_PROPID"), + DATE_CREATED = ConvertStruct(i, "DATE_CREATED"), + DATE_MODIFIED = ConvertStruct(i, "DATE_MODIFIED") + }).OrderBy(i => i.TABLE_NAME).ToList(); + result.Indexes = indexesTable.Rows.Cast().Select(i => new OleDbIndex + { + TABLE_CATALOG = ConvertString(i, "TABLE_CATALOG"), + TABLE_SCHEMA = ConvertString(i, "TABLE_SCHEMA"), + TABLE_NAME = ConvertString(i, "TABLE_NAME"), + INDEX_CATALOG = ConvertString(i, "INDEX_CATALOG"), + INDEX_SCHEMA = ConvertString(i, "INDEX_SCHEMA"), + INDEX_NAME = ConvertString(i, "INDEX_NAME"), + PRIMARY_KEY = ConvertStruct(i, "PRIMARY_KEY"), + UNIQUE = ConvertStruct(i, "UNIQUE"), + CLUSTERED = ConvertStruct(i, "CLUSTERED"), + TYPE = ConvertStruct(i, "TYPE"), + FILL_FACTOR = ConvertStruct(i, "FILL_FACTOR"), + INITIAL_SIZE = ConvertStruct(i, "INITIAL_SIZE"), + NULLS = ConvertStruct(i, "NULLS"), + SORT_BOOKMARKS = ConvertStruct(i, "SORT_BOOKMARKS"), + AUTO_UPDATE = ConvertStruct(i, "AUTO_UPDATE"), + NULL_COLLATION = ConvertStruct(i, "NULL_COLLATION"), + ORDINAL_POSITION = ConvertStruct(i, "ORDINAL_POSITION"), + COLUMN_NAME = ConvertString(i, "COLUMN_NAME"), + COLUMN_GUID = ConvertStruct(i, "COLUMN_GUID"), + COLUMN_PROPID = ConvertStruct(i, "COLUMN_PROPID"), + COLLATION = ConvertStruct(i, "COLLATION"), + CARDINALITY = ConvertStruct(i, "CARDINALITY"), + PAGES = ConvertStruct(i, "PAGES"), + FILTER_CONDITION = ConvertString(i, "FILTER_CONDITION"), + INTEGRATED = ConvertStruct(i, "INTEGRATED") + }).OrderBy(i => i.INDEX_NAME).ThenBy(i => i.ORDINAL_POSITION).ToList(); + result.KeyColumnUsages = keyColumnUsageTable.Rows.Cast().Select(i => + new OleDbKeyColumnUsage + { + CONSTRAINT_CATALOG = ConvertString(i, "CONSTRAINT_CATALOG"), + CONSTRAINT_SCHEMA = ConvertString(i, "CONSTRAINT_SCHEMA"), + CONSTRAINT_NAME = ConvertString(i, "CONSTRAINT_NAME"), + TABLE_CATALOG = ConvertString(i, "TABLE_CATALOG"), + TABLE_SCHEMA = ConvertString(i, "TABLE_SCHEMA"), + TABLE_NAME = ConvertString(i, "TABLE_NAME"), + COLUMN_NAME = ConvertString(i, "COLUMN_NAME"), + COLUMN_GUID = ConvertStruct(i, "COLUMN_GUID"), + COLUMN_PROPID = ConvertStruct(i, "COLUMN_PROPID"), + ORDINAL_POSITION = ConvertStruct(i, "ORDINAL_POSITION") + }).OrderBy(i => i.TABLE_NAME).ThenBy(i => i.ORDINAL_POSITION).ToList(); + result.ForeignKeys = foreignKeysTable.Rows.Cast().Select(i => new OleDbForeignKey + { + PK_TABLE_CATALOG = ConvertString(i, "PK_TABLE_CATALOG"), + PK_TABLE_SCHEMA = ConvertString(i, "PK_TABLE_SCHEMA"), + PK_TABLE_NAME = ConvertString(i, "PK_TABLE_NAME"), + PK_COLUMN_NAME = ConvertString(i, "PK_COLUMN_NAME"), + PK_COLUMN_GUID = ConvertStruct(i, "PK_COLUMN_GUID"), + PK_COLUMN_PROPID = ConvertStruct(i, "PK_COLUMN_PROPID"), + FK_TABLE_CATALOG = ConvertString(i, "FK_TABLE_CATALOG"), + FK_TABLE_SCHEMA = ConvertString(i, "FK_TABLE_SCHEMA"), + FK_TABLE_NAME = ConvertString(i, "FK_TABLE_NAME"), + FK_COLUMN_NAME = ConvertString(i, "FK_COLUMN_NAME"), + FK_COLUMN_GUID = ConvertStruct(i, "FK_COLUMN_GUID"), + FK_COLUMN_PROPID = ConvertStruct(i, "FK_COLUMN_PROPID"), + ORDINAL = ConvertStruct(i, "ORDINAL"), + UPDATE_RULE = ConvertString(i, "UPDATE_RULE"), + DELETE_RULE = ConvertString(i, "DELETE_RULE"), + PK_NAME = ConvertString(i, "PK_NAME"), + FK_NAME = ConvertString(i, "FK_NAME"), + DEFERRABILITY = ConvertStruct(i, "DEFERRABILITY") + }).OrderBy(i => i.FK_NAME).ThenBy(i => i.ORDINAL).ToList(); + + result.ConstraintColumnUsages = constraintUsageColumnTable.Rows.Cast().Select(i => + new OleDbConstraintColumnUsage + { + TABLE_CATALOG = ConvertString(i, "TABLE_CATALOG"), + TABLE_SCHEMA = ConvertString(i, "TABLE_SCHEMA"), + TABLE_NAME = ConvertString(i, "TABLE_NAME"), + COLUMN_NAME = ConvertString(i, "COLUMN_NAME"), + COLUMN_GUID = ConvertStruct(i, "COLUMN_GUID"), + COLUMN_PROPID = ConvertStruct(i, "COLUMN_PROPID"), + CONSTRAINT_CATALOG = ConvertString(i, "CONSTRAINT_CATALOG"), + CONSTRAINT_SCHEMA = ConvertString(i, "CONSTRAINT_SCHEMA"), + CONSTRAINT_NAME = ConvertString(i, "CONSTRAINT_NAME") + }).ToList(); + + result.Columns = columnsTable.Rows.Cast().Select(i => + { + var columns = new OleDbColumn(); + columns.TABLE_CATALOG = ConvertString(i, "TABLE_CATALOG"); + columns.TABLE_SCHEMA = ConvertString(i, "TABLE_SCHEMA"); + columns.TABLE_NAME = ConvertString(i, "TABLE_NAME"); + columns.COLUMN_NAME = ConvertString(i, "COLUMN_NAME"); + columns.COLUMN_GUID = ConvertStruct(i, "COLUMN_GUID"); + columns.COLUMN_PROPID = ConvertStruct(i, "COLUMN_PROPID"); + columns.ORDINAL_POSITION = ConvertStruct(i, "ORDINAL_POSITION"); + columns.COLUMN_HASDEFAULT = ConvertStruct(i, "COLUMN_HASDEFAULT"); + columns.COLUMN_DEFAULT = ConvertString(i, "COLUMN_DEFAULT"); + columns.COLUMN_FLAGS = ConvertStruct(i, "COLUMN_FLAGS"); + columns.IS_NULLABLE = ConvertStruct(i, "IS_NULLABLE"); + columns.DATA_TYPE = (OleDbType)i["DATA_TYPE"]; + columns.TYPE_GUID = ConvertStruct(i, "TYPE_GUID"); + columns.CHARACTER_MAXIMUM_LENGTH = ConvertStruct(i, "CHARACTER_MAXIMUM_LENGTH"); + columns.CHARACTER_OCTET_LENGTH = ConvertStruct(i, "CHARACTER_OCTET_LENGTH"); + columns.NUMERIC_PRECISION = ConvertStruct(i, "NUMERIC_PRECISION"); + columns.NUMERIC_SCALE = ConvertStruct(i, "NUMERIC_SCALE"); + columns.DATETIME_PRECISION = ConvertStruct(i, "DATETIME_PRECISION"); + columns.CHARACTER_SET_CATALOG = ConvertString(i, "CHARACTER_SET_CATALOG"); + columns.CHARACTER_SET_SCHEMA = ConvertString(i, "CHARACTER_SET_SCHEMA"); + columns.CHARACTER_SET_NAME = ConvertString(i, "CHARACTER_SET_NAME"); + columns.COLLATION_CATALOG = ConvertString(i, "COLLATION_CATALOG"); + columns.COLLATION_SCHEMA = ConvertString(i, "COLLATION_SCHEMA"); + columns.COLLATION_NAME = ConvertString(i, "COLLATION_NAME"); + columns.DOMAIN_CATALOG = ConvertString(i, "DOMAIN_CATALOG"); + columns.DOMAIN_SCHEMA = ConvertString(i, "DOMAIN_SCHEMA"); + columns.DOMAIN_NAME = ConvertString(i, "DOMAIN_NAME"); + columns.DESCRIPTION = ConvertString(i, "DESCRIPTION"); + return columns; + }).OrderBy(i => i.TABLE_NAME).ThenBy(i => i.ORDINAL_POSITION).ToList(); + + + result.Procedures = proceduresTable.Rows.Cast().Select(i => new OleDbProcedure + { + PROCEDURE_CATALOG = ConvertString(i, "PROCEDURE_CATALOG"), + PROCEDURE_SCHEMA = ConvertString(i, "PROCEDURE_SCHEMA"), + PROCEDURE_NAME = ConvertString(i, "PROCEDURE_NAME"), + PROCEDURE_TYPE = ConvertStruct(i, "PROCEDURE_TYPE"), + PROCEDURE_DEFINITION = ConvertString(i, "PROCEDURE_DEFINITION"), + DESCRIPTION = ConvertString(i, "DESCRIPTION"), + DATE_CREATED = ConvertStruct(i, "DATE_CREATED"), + DATE_MODIFIED = ConvertStruct(i, "DATE_MODIFIED") + }).OrderBy(i => i.PROCEDURE_NAME).ToList(); + + result.TableConstraints = tableConstraintsTable.Rows.Cast().Select(i => + new OleDbTableConstraint + { + CONSTRAINT_CATALOG = ConvertString(i, "CONSTRAINT_CATALOG"), + CONSTRAINT_SCHEMA = ConvertString(i, "CONSTRAINT_SCHEMA"), + CONSTRAINT_NAME = ConvertString(i, "CONSTRAINT_NAME"), + TABLE_CATALOG = ConvertString(i, "TABLE_CATALOG"), + TABLE_SCHEMA = ConvertString(i, "TABLE_SCHEMA"), + TABLE_NAME = ConvertString(i, "TABLE_NAME"), + CONSTRAINT_TYPE = ConvertString(i, "CONSTRAINT_TYPE"), + IS_DEFERRABLE = ConvertStruct(i, "IS_DEFERRABLE"), + INITIALLY_DEFERRED = ConvertStruct(i, "INITIALLY_DEFERRED"), + DESCRIPTION = ConvertString(i, "DESCRIPTION") + }).OrderBy(i => i.TABLE_NAME).ThenBy(i => i.CONSTRAINT_NAME).ToList(); + info = result; + } + finally + { + connection.Close(); + } + } + + return info; + } + + private Column CreateColumn(OleDbColumn oleDbColumn, List oleDbIndexes, + List constraintColumnUsages, DataTypeMapper dataTypeMapper, + SchemaInfo schemaInfo) + { + var tableName = oleDbColumn.TABLE_NAME; + var columnName = oleDbColumn.COLUMN_NAME; + + var primaryKeyConstraintColumnUsages = constraintColumnUsages.Where(i => + i.CONSTRAINT_NAME.Equals("PrimaryKey", StringComparison.InvariantCultureIgnoreCase)).ToList(); + var foreignKey = schemaInfo.ForeignKeys.SingleOrDefault(i => + i.FK_COLUMN_NAME == columnName && i.FK_TABLE_NAME == tableName); + + var column = new Column + { + Name = columnName, + IsNullable = oleDbColumn.IS_NULLABLE ?? false, + IsForeignKey = foreignKey != null, + ConstraintName = foreignKey?.FK_NAME, + ForeignKeyColumnName = foreignKey?.PK_COLUMN_NAME, + ForeignKeyTableName = foreignKey?.PK_TABLE_NAME, + IsIdentity = foreignKey == null && IsAutonumber(oleDbColumn) && + primaryKeyConstraintColumnUsages.Count == 1 && + primaryKeyConstraintColumnUsages.Any(i => i.COLUMN_NAME == columnName), + IsPrimaryKey = oleDbIndexes.Any(i => i.COLUMN_NAME == columnName && i.PRIMARY_KEY == true) + || primaryKeyConstraintColumnUsages.Any(i => i.COLUMN_NAME == columnName), + MappedDataType = dataTypeMapper.MapFromDBType(ServerType.MSAccess, oleDbColumn.DATA_TYPE.ToString(), + Convert.ToInt32(oleDbColumn.CHARACTER_MAXIMUM_LENGTH), oleDbColumn.NUMERIC_PRECISION, + oleDbColumn.NUMERIC_SCALE).ToString(), + DataLength = Convert.ToInt32(oleDbColumn.CHARACTER_MAXIMUM_LENGTH), + DataType = oleDbColumn.DATA_TYPE.ToString(), + IsUnique = oleDbIndexes.GroupBy(i => i.INDEX_NAME).Where(i => i.Count() == 1) + .Any(i => i.Any(index => + index.UNIQUE == true && index.PRIMARY_KEY == false && + index.COLUMN_NAME == columnName)) + }; + + return column; + } + + + private List DetermineHasManyRelationships(Table table, + List data) + { + return data.Where(i => i.PK_TABLE_NAME == table.Name).Select(i => new { i.FK_COLUMN_NAME, i.FK_TABLE_NAME }) + .Distinct().Select(i => new HasMany + { Reference = i.FK_TABLE_NAME, ReferenceColumn = i.FK_COLUMN_NAME }).ToList(); + } + + private string ConvertString(DataRow row, string columnName) + { + var tmp = row[columnName]; + if (tmp == DBNull.Value) return null; + return tmp.ToString(); + } + + private T? ConvertStruct(DataRow row, string columnName) where T : struct + { + var tmp = row[columnName]; + if (tmp == DBNull.Value) return null; + return (T)tmp; + } + + + /// + /// Search for one or more columns that make up the foreign key. + /// + /// All columns that could be used for the foreign key + /// Name of the foreign key constraint + /// List of columns associated with the foreign key + /// Composite foreign key will return multiple columns + private IList DetermineColumnsForForeignKey(IList columns, string foreignKeyName) + { + return (from c in columns + where c.IsForeignKey && c.ConstraintName == foreignKeyName + select c).ToList(); + } + + public class SchemaInfo + { + public List Indexes { get; set; } = new List(); + public List Tables { get; set; } = new List(); + public List Columns { get; set; } = new List(); + public List KeyColumnUsages { get; set; } = new List(); + public List ForeignKeys { get; set; } = new List(); + + public List ConstraintColumnUsages { get; set; } = + new List(); + + public List Procedures { get; set; } = new List(); + public List TableConstraints { get; set; } = new List(); + } + + public class OleDbColumn + { + public string TABLE_CATALOG { get; set; } + public string TABLE_SCHEMA { get; set; } + public string TABLE_NAME { get; set; } + public string COLUMN_NAME { get; set; } + public Guid? COLUMN_GUID { get; set; } + public long? COLUMN_PROPID { get; set; } + public long? ORDINAL_POSITION { get; set; } + public bool? COLUMN_HASDEFAULT { get; set; } + public string COLUMN_DEFAULT { get; set; } + public OleDbColumnFlagsEnum? COLUMN_FLAGS { get; set; } + public bool? IS_NULLABLE { get; set; } + public OleDbType DATA_TYPE { get; set; } + public Guid? TYPE_GUID { get; set; } + public long? CHARACTER_MAXIMUM_LENGTH { get; set; } + public long? CHARACTER_OCTET_LENGTH { get; set; } + public int? NUMERIC_PRECISION { get; set; } + public short? NUMERIC_SCALE { get; set; } + public long? DATETIME_PRECISION { get; set; } + public string CHARACTER_SET_CATALOG { get; set; } + public string CHARACTER_SET_SCHEMA { get; set; } + public string CHARACTER_SET_NAME { get; set; } + public string COLLATION_CATALOG { get; set; } + public string COLLATION_SCHEMA { get; set; } + public string COLLATION_NAME { get; set; } + public string DOMAIN_CATALOG { get; set; } + public string DOMAIN_SCHEMA { get; set; } + public string DOMAIN_NAME { get; set; } + public string DESCRIPTION { get; set; } + } + + public class OleDbConstraintColumnUsage + { + public string TABLE_CATALOG { get; set; } + public string TABLE_SCHEMA { get; set; } + public string TABLE_NAME { get; set; } + public string COLUMN_NAME { get; set; } + public Guid? COLUMN_GUID { get; set; } + public long? COLUMN_PROPID { get; set; } + public string CONSTRAINT_CATALOG { get; set; } + public string CONSTRAINT_SCHEMA { get; set; } + public string CONSTRAINT_NAME { get; set; } + } + + + public class OleDbForeignKey + { + public string PK_TABLE_CATALOG { get; set; } + public string PK_TABLE_SCHEMA { get; set; } + public string PK_TABLE_NAME { get; set; } + public string PK_COLUMN_NAME { get; set; } + public Guid? PK_COLUMN_GUID { get; set; } + public long? PK_COLUMN_PROPID { get; set; } + public string FK_TABLE_CATALOG { get; set; } + public string FK_TABLE_SCHEMA { get; set; } + public string FK_TABLE_NAME { get; set; } + public string FK_COLUMN_NAME { get; set; } + public Guid? FK_COLUMN_GUID { get; set; } + public long? FK_COLUMN_PROPID { get; set; } + public long? ORDINAL { get; set; } + public string UPDATE_RULE { get; set; } + public string DELETE_RULE { get; set; } + public string PK_NAME { get; set; } + public string FK_NAME { get; set; } + public short? DEFERRABILITY { get; set; } + } + + public class OleDbIndex + { + public string TABLE_CATALOG { get; set; } + public string TABLE_SCHEMA { get; set; } + public string TABLE_NAME { get; set; } + public string INDEX_CATALOG { get; set; } + public string INDEX_SCHEMA { get; set; } + public string INDEX_NAME { get; set; } + public bool? PRIMARY_KEY { get; set; } + public bool? UNIQUE { get; set; } + public bool? CLUSTERED { get; set; } + public int? TYPE { get; set; } + public int? FILL_FACTOR { get; set; } + public int? INITIAL_SIZE { get; set; } + public int? NULLS { get; set; } + public bool? SORT_BOOKMARKS { get; set; } + public bool? AUTO_UPDATE { get; set; } + public int? NULL_COLLATION { get; set; } + public long? ORDINAL_POSITION { get; set; } + public string COLUMN_NAME { get; set; } + public Guid? COLUMN_GUID { get; set; } + public long? COLUMN_PROPID { get; set; } + public short? COLLATION { get; set; } + public decimal? CARDINALITY { get; set; } + public int? PAGES { get; set; } + public string FILTER_CONDITION { get; set; } + public bool? INTEGRATED { get; set; } + } + + public class OleDbKeyColumnUsage + { + public string CONSTRAINT_CATALOG { get; set; } + public string CONSTRAINT_SCHEMA { get; set; } + public string CONSTRAINT_NAME { get; set; } + public string TABLE_CATALOG { get; set; } + public string TABLE_SCHEMA { get; set; } + public string TABLE_NAME { get; set; } + public string COLUMN_NAME { get; set; } + public Guid? COLUMN_GUID { get; set; } + public long? COLUMN_PROPID { get; set; } + public long? ORDINAL_POSITION { get; set; } + } + + + public class OleDbProcedure + { + public string PROCEDURE_CATALOG { get; set; } + public string PROCEDURE_SCHEMA { get; set; } + public string PROCEDURE_NAME { get; set; } + public short? PROCEDURE_TYPE { get; set; } + public string PROCEDURE_DEFINITION { get; set; } + public string DESCRIPTION { get; set; } + public DateTime? DATE_CREATED { get; set; } + public DateTime? DATE_MODIFIED { get; set; } + } + + + public class OleDbTableConstraint + { + public string CONSTRAINT_CATALOG { get; set; } + public string CONSTRAINT_SCHEMA { get; set; } + public string CONSTRAINT_NAME { get; set; } + public string TABLE_CATALOG { get; set; } + public string TABLE_SCHEMA { get; set; } + public string TABLE_NAME { get; set; } + public string CONSTRAINT_TYPE { get; set; } + public bool? IS_DEFERRABLE { get; set; } + public bool? INITIALLY_DEFERRED { get; set; } + public string DESCRIPTION { get; set; } + } + + public class OleDbTable + { + public string TABLE_CATALOG { get; set; } + public string TABLE_SCHEMA { get; set; } + public string TABLE_NAME { get; set; } + public string TABLE_TYPE { get; set; } + public Guid? TABLE_GUID { get; set; } + public string DESCRIPTION { get; set; } + public long? TABLE_PROPID { get; set; } + public DateTime? DATE_CREATED { get; set; } + public DateTime? DATE_MODIFIED { get; set; } + } + } +} \ No newline at end of file diff --git a/NMG.Core/Util/StringConstants.cs b/NMG.Core/Util/StringConstants.cs index cea6438..e0e5ade 100644 --- a/NMG.Core/Util/StringConstants.cs +++ b/NMG.Core/Util/StringConstants.cs @@ -27,5 +27,7 @@ public class StringConstants public static string CUBRID_CONN_STR_TEMPLATE = "server=localhost;port=33000;database=demodb;user=dba;password="; + + public static string MSACCESS_CONN_STR_TEMPLATE = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\myFolder\myAccessFile.accdb"; } } \ No newline at end of file From 1c6da73bc101c93224f9a8b7ed9057d831318445 Mon Sep 17 00:00:00 2001 From: Xavier Jefferson Date: Thu, 21 Dec 2023 14:29:30 -0800 Subject: [PATCH 2/2] In generated code, embed all string representations of raw table, column, constraint, and index names in backticks so that names with spaces or reserved words are handled properly by NHibernate. --- NMG.Core/ByCode/DBColumnMapper.cs | 33 ++++++++++--------- NMG.Core/Fluent/DBColumnMapper.cs | 3 +- NMG.Core/Generator/ByCodeGenerator.cs | 5 +-- .../Generator/EntityFrameworkGenerator.cs | 11 ++++--- NMG.Core/Generator/FluentGenerator.cs | 21 ++++++------ NMG.Core/NMG.Core.csproj | 1 + NMG.Core/Reader/MSAccessMetadataReader.cs | 2 +- NMG.Core/Util/Extensions.cs | 24 ++++++++++++++ NMG.Tests/Fluent/DBColumnMapperTest.cs | 4 +-- NMG.Tests/NMG.Tests.csproj | 2 ++ NMG.Tests/Util/StringExtensionsTest.cs | 6 ++++ NMG.Tests/packages.config | 2 ++ 12 files changed, 77 insertions(+), 37 deletions(-) diff --git a/NMG.Core/ByCode/DBColumnMapper.cs b/NMG.Core/ByCode/DBColumnMapper.cs index c88b81c..8eb00c6 100644 --- a/NMG.Core/ByCode/DBColumnMapper.cs +++ b/NMG.Core/ByCode/DBColumnMapper.cs @@ -4,6 +4,7 @@ using NMG.Core.Domain; using NMG.Core.TextFormatter; using System.Text; +using NMG.Core.Util; namespace NMG.Core.ByCode { @@ -32,15 +33,15 @@ public string IdSequenceMap(Column column, string sequenceName, ITextFormatter f builder.AppendFormat("Id(x => x.{0}, map => ", formatter.FormatText(column.Name)); builder.AppendLine(); builder.AppendLine("\t\t\t\t{"); - builder.AppendLine("\t\t\t\t\tmap.Column(\"" + column.Name + "\");"); - builder.AppendLine("\t\t\t\t\tmap.Generator(Generators.Sequence, g => g.Params(new { sequence = \"" + sequenceName + "\" }));"); + builder.AppendLine("\t\t\t\t\tmap.Column(" + column.Name.ToStringLiteral() + ");"); + builder.AppendLine("\t\t\t\t\tmap.Generator(Generators.Sequence, g => g.Params(new { sequence = " + sequenceName.ToStringLiteral() + " }));"); builder.Append("\t\t\t\t});"); break; case Language.VB: builder.AppendFormat("Id(Function(x) x.{0}, Sub(map)", formatter.FormatText(column.Name)); builder.AppendLine(); - builder.AppendLine("\t\t\t\t\tmap.Column(\"" + column.Name + "\")"); - builder.AppendLine("\t\t\t\t\tmap.Generator(Generators.Sequence, Function(g) g.Params(New { sequence = \"" + sequenceName + "\" }))"); + builder.AppendLine("\t\t\t\t\tmap.Column(" + column.Name.ToStringLiteral() + ")"); + builder.AppendLine("\t\t\t\t\tmap.Generator(Generators.Sequence, Function(g) g.Params(New { sequence = " + sequenceName.ToStringLiteral() + " }))"); builder.Append("\t\t\t\tEnd Sub)"); break; } @@ -55,7 +56,7 @@ public string IdMap (Column column, ITextFormatter formatter) if (column.Name.ToLower() != propertyName.ToLower()) { - mapList.Add("map.Column(\"" + column.Name + "\")"); + mapList.Add("map.Column(" + column.Name.ToStringLiteral() + ")"); } mapList.Add(column.IsIdentity ? "map.Generator(Generators.Identity)" : "map.Generator(Generators.Assigned)"); @@ -75,7 +76,7 @@ public string CompositeIdMap(IList columns, ITextFormatter formatter) builder.AppendLine("\t\t\t\t{"); foreach (var column in columns) { - builder.AppendLine("\t\t\t\t\tcompId.Property(x => x." + formatter.FormatText(column.Name) + ", m => m.Column(\"" + column.Name + "\"));"); + builder.AppendLine("\t\t\t\t\tcompId.Property(x => x." + formatter.FormatText(column.Name) + ", m => m.Column(" + column.Name.ToStringLiteral() + "));"); } builder.Append("\t\t\t\t});"); break; @@ -83,7 +84,7 @@ public string CompositeIdMap(IList columns, ITextFormatter formatter) builder.AppendLine("ComposedId(Sub(compId)"); foreach (var column in columns) { - builder.AppendLine("\t\t\t\t\tcompId.Property(Function(x) x." + formatter.FormatText(column.Name) + ", Sub(m) m.Column(\"" + column.Name + "\"))"); + builder.AppendLine("\t\t\t\t\tcompId.Property(Function(x) x." + formatter.FormatText(column.Name) + ", Sub(m) m.Column(" + column.Name.ToStringLiteral() + "))"); } builder.AppendLine("\t\t\t\tEnd Sub)"); break; @@ -108,7 +109,7 @@ public string Map(Column column, ITextFormatter formatter, bool includeLengthAnd // Column if (column.Name.ToLower() != propertyName.ToLower()) { - mapList.Add("map.Column(\"" + column.Name + "\")"); + mapList.Add("map.Column(" + column.Name.ToStringLiteral() + ")"); } // Not Null if (!column.IsNullable) @@ -200,16 +201,16 @@ public string Bag(HasMany hasMany, ITextFormatter formatter) if (_language == Language.CSharp) { builder.AppendFormat( - "\t\t\tBag(x => x.{0}, colmap => {{ colmap.Key(x => x.Column(\"{1}\")); colmap.Inverse(true); }}, map => {{ map.OneToMany(); }});", + "\t\t\tBag(x => x.{0}, colmap => {{ colmap.Key(x => x.Column({1})); colmap.Inverse(true); }}, map => {{ map.OneToMany(); }});", formatter.FormatPlural(hasMany.Reference), - hasMany.ReferenceColumn); + hasMany.ReferenceColumn.ToStringLiteral()); } else if (_language == Language.VB) { builder.AppendFormat( - "\t\t\tBag(Function(x) x.{0}, Sub(colmap) colmap.Key(Function(x) x.Column(\"{1}\")), Sub(map) map.OneToMany())", + "\t\t\tBag(Function(x) x.{0}, Sub(colmap) colmap.Key(Function(x) x.Column({1})), Sub(map) map.OneToMany())", formatter.FormatPlural(hasMany.Reference), - hasMany.ReferenceColumn); + hasMany.ReferenceColumn.ToStringLiteral()); } return builder.ToString(); } @@ -220,12 +221,12 @@ public string Reference(ForeignKey fk, ITextFormatter formatter) if (fk.Columns.Count() == 1) { var mapList = new List(); - mapList.Add("map.Column(\"" + fk.Columns.First().Name + "\")"); + mapList.Add("map.Column(" + fk.Columns.First().Name.ToStringLiteral() + ")"); // PropertyRef - Used with a FK that doesnt map to a primary key on referenced table. if (!string.IsNullOrEmpty(fk.Columns.First().ForeignKeyColumnName)) { - mapList.Add("map.PropertyRef(\"" + formatter.FormatText(fk.Columns.First().ForeignKeyColumnName) + "\")"); + mapList.Add("map.PropertyRef(" + formatter.FormatText(fk.Columns.First().ForeignKeyColumnName).ToStringLiteral() + ")"); } if (!fk.Columns.First().IsNullable) { @@ -247,7 +248,7 @@ public string Reference(ForeignKey fk, ITextFormatter formatter) var lastColumn = fk.Columns.Last(); foreach (var column in fk.Columns) { - builder.AppendFormat("x => x.Name(\"{0}\")", column.Name); + builder.AppendFormat("x => x.Name({0})", column.Name.ToStringLiteral()); var isLastColumn = lastColumn == column; if (!isLastColumn) @@ -267,7 +268,7 @@ public string Reference(ForeignKey fk, ITextFormatter formatter) var lastColumn = fk.Columns.Last(); foreach (var column in fk.Columns) { - builder.AppendFormat("x.Name(\"{0}\")", column.Name); + builder.AppendFormat("x.Name({0})", column.Name.ToStringLiteral()); var isLastColumn = lastColumn == column; if (!isLastColumn) diff --git a/NMG.Core/Fluent/DBColumnMapper.cs b/NMG.Core/Fluent/DBColumnMapper.cs index dd51ae1..512f1ae 100644 --- a/NMG.Core/Fluent/DBColumnMapper.cs +++ b/NMG.Core/Fluent/DBColumnMapper.cs @@ -1,6 +1,7 @@ using System.Text; using NMG.Core.Domain; using NMG.Core.TextFormatter; +using NMG.Core.Util; namespace NMG.Core.Fluent { @@ -10,7 +11,7 @@ public string Map(Column column, string fieldName, ITextFormatter Formatter, boo { var mappedStrBuilder = new StringBuilder(string.Format("Map(x => x.{0})", fieldName)); mappedStrBuilder.Append(Constants.Dot); - mappedStrBuilder.Append("Column(\"" + column.Name + "\")"); + mappedStrBuilder.Append("Column(" + column.Name.ToStringLiteral() + ")"); if (!column.IsNullable) { diff --git a/NMG.Core/Generator/ByCodeGenerator.cs b/NMG.Core/Generator/ByCodeGenerator.cs index 2ba3301..6332854 100644 --- a/NMG.Core/Generator/ByCodeGenerator.cs +++ b/NMG.Core/Generator/ByCodeGenerator.cs @@ -4,6 +4,7 @@ using NMG.Core.Domain; using NMG.Core.ByCode; using NMG.Core.TextFormatter; +using NMG.Core.Util; namespace NMG.Core.Generator { @@ -61,12 +62,12 @@ public CodeCompileUnit GetCompleteCompileUnit(string mapName) // Table Name - Only ouput if table is different than the class name. if (Table.Name.ToLower() != className.ToLower()) { - constructor.Statements.Add(new CodeSnippetStatement(TABS + "Table(\"" + Table.Name + "\");")); + constructor.Statements.Add(new CodeSnippetStatement(TABS + "Table(" + Table.Name.ToStringLiteral() + ");")); } // Scheme / Owner Name if (!string.IsNullOrEmpty(Table.Owner)) { - constructor.Statements.Add(new CodeSnippetStatement(TABS + "Schema(\"" + Table.Owner + "\");")); + constructor.Statements.Add(new CodeSnippetStatement(TABS + "Schema(" + Table.Owner.ToStringLiteral() + ");")); } constructor.Statements.Add(new CodeSnippetStatement(TABS + string.Format("Lazy({0});", appPrefs.UseLazy ? "true" : "false"))); diff --git a/NMG.Core/Generator/EntityFrameworkGenerator.cs b/NMG.Core/Generator/EntityFrameworkGenerator.cs index 595cc83..5857333 100644 --- a/NMG.Core/Generator/EntityFrameworkGenerator.cs +++ b/NMG.Core/Generator/EntityFrameworkGenerator.cs @@ -5,6 +5,7 @@ using NMG.Core.Domain; using NMG.Core.Fluent; using NMG.Core.TextFormatter; +using NMG.Core.Util; namespace NMG.Core.Generator { @@ -53,14 +54,14 @@ public CodeCompileUnit GetCompleteCompileUnit(string className) newType.BaseTypes.Add(string.Format("EntityTypeConfiguration<{0}{1}>", appPrefs.ClassNamePrefix, pascalCaseTextFormatter.FormatSingular(Table.Name))); var constructor = new CodeConstructor {Attributes = MemberAttributes.Public}; - constructor.Statements.Add(new CodeSnippetStatement(TABS + "ToTable(\"" + Table.Name + "\");")); + constructor.Statements.Add(new CodeSnippetStatement(TABS + "ToTable(" + Table.Name.ToStringLiteral() + ");")); if (appPrefs.UseLazy) constructor.Statements.Add(new CodeSnippetStatement(TABS + "LazyLoad();")); if (UsesSequence) { var fieldName = FixPropertyWithSameClassName(Table.PrimaryKey.Columns[0].Name, Table.Name); - constructor.Statements.Add(new CodeSnippetStatement(String.Format(TABS + "Id(x => x.{0}).Column(x => x.{1}).GeneratedBy.Sequence(\"{2}\")", Formatter.FormatText(fieldName), fieldName, appPrefs.Sequence))); + constructor.Statements.Add(new CodeSnippetStatement(String.Format(TABS + "Id(x => x.{0}).Column(x => x.{1}).GeneratedBy.Sequence({2})", Formatter.FormatText(fieldName), fieldName, appPrefs.Sequence.ToStringLiteral()))); } else if (Table.PrimaryKey != null && Table.PrimaryKey.Type == PrimaryKeyType.PrimaryKey) { @@ -127,7 +128,7 @@ private static CodeSnippetStatement GetIdMapCodeSnippetStatement(ApplicationPref var fieldName = FixPropertyWithSameClassName(propertyName, table.Name); var pkAlsoFkQty = (from fk in table.ForeignKeys.Where(fk => fk.UniquePropertyName == pkColumnName) select fk).Count(); if (pkAlsoFkQty > 0) fieldName = fieldName + "Id"; - return new CodeSnippetStatement(string.Format(TABS + "Id(x => x.{0}).{1}.Column(\"{2}\");", formatter.FormatText(fieldName), idGeneratorType, pkColumnName)); + return new CodeSnippetStatement(string.Format(TABS + "Id(x => x.{0}).{1}.Column({2});", formatter.FormatText(fieldName), idGeneratorType, pkColumnName.ToStringLiteral())); } private static CodeSnippetStatement GetIdMapCodeSnippetStatement(PrimaryKey primaryKey, Table table, ITextFormatter formatter) @@ -140,7 +141,7 @@ private static CodeSnippetStatement GetIdMapCodeSnippetStatement(PrimaryKey prim var fieldName = FixPropertyWithSameClassName(propertyName, table.Name); var pkAlsoFkQty = (from fk in table.ForeignKeys.Where(fk => fk.UniquePropertyName == pkColumn.Name) select fk).Count(); if (pkAlsoFkQty > 0) fieldName = fieldName + "Id"; - var tmp = String.Format(".KeyProperty(x => x.{0}, \"{1}\")", fieldName, pkColumn.Name); + var tmp = String.Format(".KeyProperty(x => x.{0}, {1})", fieldName, pkColumn.Name.ToStringLiteral()); keyPropertyBuilder.Append(first ? tmp : "\n" + TABS + " " + tmp); first = false; } @@ -164,7 +165,7 @@ public EFOneToMany(ITextFormatter formatter, PascalCaseTextFormatter pascalCaseT public CodeSnippetStatement Create(HasMany hasMany) { var hasManySnippet = string.Format("HasMany(x => x.{0}).WithMany(x => x.{1})", Formatter.FormatPlural(hasMany.Reference), pascalCaseTextFormatter.FormatSingular(hasMany.PKTableName)); - var keySnippet = string.Format(".Map(m => {{m.ToTable(\"{0}\"); m.MapLeftKey(\"{1}\"); m.MapRightKey(\"{2}\");}})", hasMany.Reference, hasMany.ReferenceColumn, hasMany.ReferenceColumn); + var keySnippet = string.Format(".Map(m => {{m.ToTable({0}); m.MapLeftKey({1}); m.MapRightKey({2});}})", hasMany.Reference.ToStringLiteral(), hasMany.ReferenceColumn.ToStringLiteral(), hasMany.ReferenceColumn.ToStringLiteral()); return new CodeSnippetStatement(string.Format(AbstractGenerator.TABS + "{0}{1};", hasManySnippet, keySnippet)); } } diff --git a/NMG.Core/Generator/FluentGenerator.cs b/NMG.Core/Generator/FluentGenerator.cs index 2cabf58..23dc47e 100644 --- a/NMG.Core/Generator/FluentGenerator.cs +++ b/NMG.Core/Generator/FluentGenerator.cs @@ -6,6 +6,7 @@ using NMG.Core.Domain; using NMG.Core.Fluent; using NMG.Core.TextFormatter; +using NMG.Core.Util; namespace NMG.Core.Generator { @@ -53,15 +54,15 @@ public CodeCompileUnit GetCompleteCompileUnit(string className) newType.BaseTypes.Add(string.Format("ClassMap<{0}{1}>", appPrefs.ClassNamePrefix, pascalCaseTextFormatter.FormatSingular(Table.Name))); var constructor = new CodeConstructor {Attributes = MemberAttributes.Public}; - constructor.Statements.Add(new CodeSnippetStatement(TABS + "Table(\"" + Table.Name + "\");")); + constructor.Statements.Add(new CodeSnippetStatement(TABS + "Table(" + Table.Name.ToStringLiteral() + ");")); if (appPrefs.UseLazy) constructor.Statements.Add(new CodeSnippetStatement(TABS + "LazyLoad();")); if(UsesSequence) { var fieldName = FixPropertyWithSameClassName(Table.PrimaryKey.Columns[0].Name, Table.Name); - constructor.Statements.Add(new CodeSnippetStatement(String.Format(TABS + "Id(x => x.{0}).Column(x => x.{1}).GeneratedBy.Sequence(\"{2}\")", - Formatter.FormatText(fieldName), fieldName, appPrefs.Sequence))); + constructor.Statements.Add(new CodeSnippetStatement(String.Format(TABS + "Id(x => x.{0}).Column(x => x.{1}).GeneratedBy.Sequence({2})", + Formatter.FormatText(fieldName), fieldName, appPrefs.Sequence.ToStringLiteral()))); } else if (Table.PrimaryKey !=null && Table.PrimaryKey.Type == PrimaryKeyType.PrimaryKey) { @@ -83,11 +84,11 @@ public CodeCompileUnit GetCompleteCompileUnit(string className) var pkAlsoFkQty = (from fks in Table.ForeignKeys.Where(fks => fks.UniquePropertyName == name) select fks).Count(); if (pkAlsoFkQty > 1) { - constructor.Statements.Add(new CodeSnippetStatement(string.Format(TABS + "References(x => x.{0}).Column(\"{1}\").ForeignKey(\"{2}\");", fieldName, fk.Columns.First().Name, fk.Columns.First().ConstraintName))); + constructor.Statements.Add(new CodeSnippetStatement(string.Format(TABS + "References(x => x.{0}).Column({1}).ForeignKey({2});", fieldName, fk.Columns.First().Name.ToStringLiteral(), fk.Columns.First().ConstraintName.ToStringLiteral()))); } else { - constructor.Statements.Add(new CodeSnippetStatement(string.Format(TABS + "References(x => x.{0}).Column(\"{1}\");", fieldName, fk.Columns.First().Name))); + constructor.Statements.Add(new CodeSnippetStatement(string.Format(TABS + "References(x => x.{0}).Column({1});", fieldName, fk.Columns.First().Name.ToStringLiteral()))); } } @@ -135,10 +136,10 @@ private static CodeSnippetStatement GetIdMapCodeSnippetStatement(ApplicationPref fieldName = fieldName + "Id"; } - return new CodeSnippetStatement(string.Format(TABS + "Id(x => x.{0}).{1}.Column(\"{2}\");", + return new CodeSnippetStatement(string.Format(TABS + "Id(x => x.{0}).{1}.Column({2});", formatter.FormatText(fieldName), idGeneratorType, - pkColumnName)); + pkColumnName.ToStringLiteral())); } private static CodeSnippetStatement GetIdMapCodeSnippetStatement(PrimaryKey primaryKey, Table table, ITextFormatter formatter) @@ -155,7 +156,7 @@ private static CodeSnippetStatement GetIdMapCodeSnippetStatement(PrimaryKey prim fieldName = fieldName + "Id"; } - var tmp = String.Format(".KeyProperty(x => x.{0}, \"{1}\")",fieldName, pkColumn.Name); + var tmp = String.Format(".KeyProperty(x => x.{0}, {1})",fieldName, pkColumn.Name.ToStringLiteral()); keyPropertyBuilder.Append(first ? tmp : "\n" + TABS + " " + tmp); first = false; } @@ -189,8 +190,8 @@ public CodeSnippetStatement Create(HasMany hasMany) { var hasManySnippet = string.Format("HasMany(x => x.{0})", Formatter.FormatPlural(hasMany.Reference)); var keySnippet = hasMany.AllReferenceColumns.Count == 1 ? - string.Format(".KeyColumn(\"{0}\")", hasMany.ReferenceColumn) : - string.Format(".KeyColumns({0})", hasMany.AllReferenceColumns.Aggregate("new string[] { ", (a, b) => a + "\"" + b + "\", ", c => c.Substring(0, c.Length - 2) + " }")); + string.Format(".KeyColumn({0})", hasMany.ReferenceColumn.ToStringLiteral()) : + string.Format(".KeyColumns({0})", hasMany.AllReferenceColumns.Aggregate("new string[] { ", (a, b) => a + b.ToStringLiteral() + ", ", c => c.Substring(0, c.Length - 2) + " }")); return new CodeSnippetStatement(string.Format(AbstractGenerator.TABS + "{0}{1};", hasManySnippet, keySnippet)); } diff --git a/NMG.Core/NMG.Core.csproj b/NMG.Core/NMG.Core.csproj index 39ed73b..0790848 100644 --- a/NMG.Core/NMG.Core.csproj +++ b/NMG.Core/NMG.Core.csproj @@ -222,6 +222,7 @@ + diff --git a/NMG.Core/Reader/MSAccessMetadataReader.cs b/NMG.Core/Reader/MSAccessMetadataReader.cs index 30e54c6..aa46146 100644 --- a/NMG.Core/Reader/MSAccessMetadataReader.cs +++ b/NMG.Core/Reader/MSAccessMetadataReader.cs @@ -92,7 +92,7 @@ public IList GetTableDetails(Table table, string owner) public IList GetOwners() { - return new List { "dbo" }; + return new List { "master" }; } public List GetSequences(string owner) diff --git a/NMG.Core/Util/Extensions.cs b/NMG.Core/Util/Extensions.cs index 0d96ce1..18d00c2 100644 --- a/NMG.Core/Util/Extensions.cs +++ b/NMG.Core/Util/Extensions.cs @@ -65,5 +65,29 @@ public static string MakeTitleCase(this string text) TextInfo textInfo = cultureInfo.TextInfo; return textInfo.ToTitleCase(text); } + + /// + /// Wrap word in double quotes, then backticks (`) + /// for NHibernate to interpret + /// without backticks + /// + /// This is for table / column / object + /// names that contain spaces or that use + /// db-specific keywords + /// so NHibernate will behave correctly + /// + /// Reference: + /// https://groups.google.com/g/nhusers/c/-46QXkkXVV0 + /// https://sdesmedt.wordpress.com/2006/09/04/nhibernate-part-4-mapping-techniques-for-aggregation-one-to-many-mapping/ + /// and from Hibernate: + /// https://stackoverflow.com/questions/50783644/add-backticks-to-column-names-in-hibernate + /// + /// + /// + public static string ToStringLiteral(this string input) + { + if (input == null) return null; + return string.Format("\"`{0}`\"", input); + } } } \ No newline at end of file diff --git a/NMG.Tests/Fluent/DBColumnMapperTest.cs b/NMG.Tests/Fluent/DBColumnMapperTest.cs index aa03479..db519f9 100644 --- a/NMG.Tests/Fluent/DBColumnMapperTest.cs +++ b/NMG.Tests/Fluent/DBColumnMapperTest.cs @@ -18,7 +18,7 @@ public void ShouldMapDBColumn() DataType = "Int", IsNullable = true }; - Assert.That(mapper.Map(column, "Age", new PascalCaseTextFormatter()), Is.EqualTo("Map(x => x.Age).Column(\"Age\");")); + Assert.That(mapper.Map(column, "Age", new PascalCaseTextFormatter()), Is.EqualTo("Map(x => x.Age).Column(\"`Age`\");")); } [Test] @@ -35,7 +35,7 @@ public void ShouldMapDBColumnWithProperties() IsPrimaryKey = false, MappedDataType = "string" }; - Assert.That(mapper.Map(column, "Name", new PascalCaseTextFormatter()), Is.EqualTo("Map(x => x.Name).Column(\"Name\").Not.Nullable().Length(16);")); + Assert.That(mapper.Map(column, "Name", new PascalCaseTextFormatter()), Is.EqualTo("Map(x => x.Name).Column(\"`Name`\").Not.Nullable().Length(16);")); } } } \ No newline at end of file diff --git a/NMG.Tests/NMG.Tests.csproj b/NMG.Tests/NMG.Tests.csproj index 654c69d..b8a68b3 100644 --- a/NMG.Tests/NMG.Tests.csproj +++ b/NMG.Tests/NMG.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -136,5 +137,6 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + \ No newline at end of file diff --git a/NMG.Tests/Util/StringExtensionsTest.cs b/NMG.Tests/Util/StringExtensionsTest.cs index 153e455..b62f8f9 100644 --- a/NMG.Tests/Util/StringExtensionsTest.cs +++ b/NMG.Tests/Util/StringExtensionsTest.cs @@ -6,6 +6,12 @@ namespace NMG.Tests.Util [TestFixture] public class StringExtensionsTest { + [Test] + public void StringLiteral() + { + Assert.AreEqual("\"`Snow Ball`\"", "Snow Ball".ToStringLiteral()); + } + [Test] public void FirstCharAsLower() { diff --git a/NMG.Tests/packages.config b/NMG.Tests/packages.config index 5532d0c..9b5f278 100644 --- a/NMG.Tests/packages.config +++ b/NMG.Tests/packages.config @@ -1,4 +1,6 @@  + + \ No newline at end of file