using System; using System.Collections.Generic; using System.Reflection; using Serilog; using SyncEngine.Configuration; namespace SyncEngine.Access { /// COM helpers for CurrentDb QueryDefs/Relations via Access.Application. internal static class AccessDaoHelper { internal static void ReadRelations( object database, IList keys, HashSet tableSet, ILogger logger) { var relations = database.GetType().InvokeMember( "Relations", BindingFlags.GetProperty, null, database, null); var count = Convert.ToInt32(relations.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, relations, null)); for (var i = 0; i < count; i++) { object relation = null; try { relation = relations.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, relations, new object[] { i }); var childTable = Convert.ToString(relation.GetType().InvokeMember( "ForeignTable", BindingFlags.GetProperty, null, relation, null)); var parentTable = Convert.ToString(relation.GetType().InvokeMember( "Table", BindingFlags.GetProperty, null, relation, null)); if (!tableSet.Contains(childTable) || !tableSet.Contains(parentTable)) { continue; } var attributes = Convert.ToInt32(relation.GetType().InvokeMember( "Attributes", BindingFlags.GetProperty, null, relation, null)); AccessSchemaReader.ParseRelationGrbit(attributes, out var onUpdateCascade, out var onDeleteCascade); var fields = relation.GetType().InvokeMember( "Fields", BindingFlags.GetProperty, null, relation, null); var fieldCount = Convert.ToInt32(fields.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, fields, null)); for (var f = 0; f < fieldCount; f++) { object field = null; try { field = fields.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, fields, new object[] { f }); var childColumn = Convert.ToString(field.GetType().InvokeMember( "Name", BindingFlags.GetProperty, null, field, null)); var parentColumn = Convert.ToString(field.GetType().InvokeMember( "ForeignName", BindingFlags.GetProperty, null, field, null)); keys.Add(new ForeignKeyInfo { ChildTable = childTable, ChildColumn = childColumn, ParentTable = parentTable, ParentColumn = parentColumn, ConstraintName = "FK_" + childTable + "_" + parentTable, OnUpdateCascade = onUpdateCascade, OnDeleteCascade = onDeleteCascade }); } catch (Exception ex) { if (logger != null) { logger.Debug("Skipped relation field {Index} ({Message})", f, FormatException(ex)); } } finally { ReleaseCom(field); } } ReleaseCom(fields); } catch (Exception ex) { if (logger != null) { logger.Debug("Skipped relation index {Index} ({Message})", i, FormatException(ex)); } } finally { ReleaseCom(relation); } } ReleaseCom(relations); } internal static void ReadFieldMetadata( object database, IList tables, HashSet tableSet, ILogger logger) { var tableMap = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var table in tables) { tableMap[table.TableName] = table; } object tableDefs = null; try { tableDefs = database.GetType().InvokeMember( "TableDefs", BindingFlags.GetProperty, null, database, null); var count = Convert.ToInt32(tableDefs.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, tableDefs, null)); for (var i = 0; i < count; i++) { object tableDef = null; try { tableDef = tableDefs.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, tableDefs, new object[] { i }); var tableName = Convert.ToString(tableDef.GetType().InvokeMember( "Name", BindingFlags.GetProperty, null, tableDef, null)); if (!tableSet.Contains(tableName) || !tableMap.TryGetValue(tableName, out var schema)) { continue; } try { var tableRule = Convert.ToString(tableDef.GetType().InvokeMember( "ValidationRule", BindingFlags.GetProperty, null, tableDef, null)); if (!string.IsNullOrWhiteSpace(tableRule)) { schema.TableValidationRule = tableRule.Trim(); } } catch (Exception ex) { logger?.Debug("Skipped TableDef.ValidationRule on {Table} ({Message})", tableName, FormatException(ex)); } ReadFieldProperties(tableDef, schema, logger); } catch (Exception ex) { logger?.Debug("Skipped TableDef metadata {Index} ({Message})", i, FormatException(ex)); } finally { ReleaseCom(tableDef); } } } finally { ReleaseCom(tableDefs); } } private static void ReadFieldProperties(object tableDef, AccessTableSchema schema, ILogger logger) { object fields = null; try { fields = tableDef.GetType().InvokeMember( "Fields", BindingFlags.GetProperty, null, tableDef, null); var count = Convert.ToInt32(fields.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, fields, null)); var columnMap = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var col in schema.Columns) { columnMap[col.Name] = col; } for (var f = 0; f < count; f++) { object field = null; try { field = fields.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, fields, new object[] { f }); var fieldName = Convert.ToString(field.GetType().InvokeMember( "Name", BindingFlags.GetProperty, null, field, null)); if (!columnMap.TryGetValue(fieldName, out var column)) { continue; } var defaultValue = Convert.ToString(field.GetType().InvokeMember( "DefaultValue", BindingFlags.GetProperty, null, field, null)); if (!string.IsNullOrWhiteSpace(defaultValue)) { column.DefaultValue = defaultValue.Trim(); } var validationRule = Convert.ToString(field.GetType().InvokeMember( "ValidationRule", BindingFlags.GetProperty, null, field, null)); if (!string.IsNullOrWhiteSpace(validationRule)) { column.ValidationRule = validationRule.Trim(); } } catch (Exception ex) { logger?.Debug("Skipped field metadata {Index} ({Message})", f, FormatException(ex)); } finally { ReleaseCom(field); } } } finally { ReleaseCom(fields); } } internal static void ReadTableIndexes( object database, IList indexes, HashSet tableSet, ILogger logger) { object tableDefs = null; try { tableDefs = database.GetType().InvokeMember( "TableDefs", BindingFlags.GetProperty, null, database, null); var count = Convert.ToInt32(tableDefs.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, tableDefs, null)); for (var i = 0; i < count; i++) { object tableDef = null; try { tableDef = tableDefs.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, tableDefs, new object[] { i }); var tableName = Convert.ToString(tableDef.GetType().InvokeMember( "Name", BindingFlags.GetProperty, null, tableDef, null)); if (!tableSet.Contains(tableName)) { continue; } ReadIndexesForTable(tableDef, tableName, indexes, logger); } catch (Exception ex) { if (logger != null) { logger.Debug("Skipped TableDef index {Index} ({Message})", i, FormatException(ex)); } } finally { ReleaseCom(tableDef); } } } finally { ReleaseCom(tableDefs); } } private static void ReadIndexesForTable( object tableDef, string tableName, IList indexes, ILogger logger) { object indexCollection = null; try { indexCollection = tableDef.GetType().InvokeMember( "Indexes", BindingFlags.GetProperty, null, tableDef, null); var count = Convert.ToInt32(indexCollection.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, indexCollection, null)); for (var i = 0; i < count; i++) { object index = null; try { index = indexCollection.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, indexCollection, new object[] { i }); var indexName = Convert.ToString(index.GetType().InvokeMember( "Name", BindingFlags.GetProperty, null, index, null)); if (string.IsNullOrEmpty(indexName) || indexName.StartsWith("~", StringComparison.Ordinal)) { continue; } var isPrimary = Convert.ToBoolean(index.GetType().InvokeMember( "Primary", BindingFlags.GetProperty, null, index, null)); var isUnique = Convert.ToBoolean(index.GetType().InvokeMember( "Unique", BindingFlags.GetProperty, null, index, null)); var columns = ReadIndexColumns(index, logger); if (columns.Count == 0) { continue; } indexes.Add(new AccessIndexInfo { TableName = tableName, IndexName = indexName, IsPrimary = isPrimary, IsUnique = isUnique || isPrimary, Columns = columns }); } catch (Exception ex) { if (logger != null) { logger.Debug( "Skipped index {Index} on {Table} ({Message})", i, tableName, FormatException(ex)); } } finally { ReleaseCom(index); } } } finally { ReleaseCom(indexCollection); } } private static IList ReadIndexColumns(object index, ILogger logger) { var columns = new List(); object fields = null; try { fields = index.GetType().InvokeMember( "Fields", BindingFlags.GetProperty, null, index, null); var count = Convert.ToInt32(fields.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, fields, null)); for (var f = 0; f < count; f++) { object field = null; try { field = fields.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, fields, new object[] { f }); var columnName = Convert.ToString(field.GetType().InvokeMember( "Name", BindingFlags.GetProperty, null, field, null)); if (!string.IsNullOrWhiteSpace(columnName)) { columns.Add(columnName); } } catch (Exception ex) { if (logger != null) { logger.Debug("Skipped index field {Index} ({Message})", f, FormatException(ex)); } } finally { ReleaseCom(field); } } } finally { ReleaseCom(fields); } return columns; } internal static void ReadQueryDefs(object database, IList queries, ILogger logger) { const int dbQSelect = 0; var queryDefs = database.GetType().InvokeMember( "QueryDefs", BindingFlags.GetProperty, null, database, null); var count = Convert.ToInt32(queryDefs.GetType().InvokeMember( "Count", BindingFlags.GetProperty, null, queryDefs, null)); for (var i = 0; i < count; i++) { object queryDef = null; try { queryDef = queryDefs.GetType().InvokeMember( "Item", BindingFlags.GetProperty, null, queryDefs, new object[] { i }); var name = Convert.ToString(queryDef.GetType().InvokeMember( "Name", BindingFlags.GetProperty, null, queryDef, null)); if (string.IsNullOrEmpty(name) || name.StartsWith("~", StringComparison.Ordinal)) { continue; } var queryType = Convert.ToInt32(queryDef.GetType().InvokeMember( "Type", BindingFlags.GetProperty, null, queryDef, null)); if (queryType != dbQSelect) { continue; } var sqlText = Convert.ToString(queryDef.GetType().InvokeMember( "SQL", BindingFlags.GetProperty, null, queryDef, null)); var query = new AccessQuerySchema { Name = name, SqlText = sqlText ?? "" }; AccessSchemaReader.ClassifyQuery(query); queries.Add(query); } catch (Exception ex) { if (logger != null) { logger.Debug("Skipped QueryDef index {Index} ({Message})", i, FormatException(ex)); } } finally { ReleaseCom(queryDef); } } ReleaseCom(queryDefs); } internal static void ReadQueryDefs(object database, IList queries) { ReadQueryDefs(database, queries, null); } internal static string FormatException(Exception ex) { var current = ex; while (current is TargetInvocationException && current.InnerException != null) { current = current.InnerException; } return current.Message; } internal static void ReleaseCom(object comObject) { if (comObject == null) { return; } try { System.Runtime.InteropServices.Marshal.ReleaseComObject(comObject); } catch { // ignore release errors during cleanup } } } }