From adff515d64c98a89dd6cb62e246ac329680efc24 Mon Sep 17 00:00:00 2001 From: srober12 Date: Sat, 16 Aug 2014 00:03:48 -0400 Subject: [PATCH 1/2] Added support for Synonyms for ADO/SQL Server --- Simple.Data.Ado/Schema/DatabaseSchema.cs | 4 +- Simple.Data.Ado/Schema/Procedure.cs | 12 ++- Simple.Data.Ado/Schema/Table.cs | 14 +++- Simple.Data.SqlServer/SqlSchemaProvider.cs | 85 +++++++++++++++++++--- 4 files changed, 99 insertions(+), 16 deletions(-) diff --git a/Simple.Data.Ado/Schema/DatabaseSchema.cs b/Simple.Data.Ado/Schema/DatabaseSchema.cs index 2a1e6265..3026cc19 100644 --- a/Simple.Data.Ado/Schema/DatabaseSchema.cs +++ b/Simple.Data.Ado/Schema/DatabaseSchema.cs @@ -98,7 +98,7 @@ private string DefaultSchema private TableCollection CreateTableCollection() { return new TableCollection(_schemaProvider.GetTables() - .Select(table => new Table(table.ActualName, table.Schema, table.Type, this))); + .Select(table => new Table(table.ActualName, table.Schema, table.Type, this, table.AliasedTable))); } private ProcedureCollection CreateProcedureCollection() @@ -107,7 +107,7 @@ private ProcedureCollection CreateProcedureCollection() .Select( proc => new Procedure(proc.Name, proc.SpecificName, proc.Schema, - this)), _schemaProvider.GetDefaultSchema()); + this, proc.AliasedProcedure)), _schemaProvider.GetDefaultSchema()); } public string QuoteObjectName(string unquotedName) diff --git a/Simple.Data.Ado/Schema/Procedure.cs b/Simple.Data.Ado/Schema/Procedure.cs index 68b5f68b..b9738730 100644 --- a/Simple.Data.Ado/Schema/Procedure.cs +++ b/Simple.Data.Ado/Schema/Procedure.cs @@ -13,23 +13,26 @@ public class Procedure private readonly string _name; private readonly string _specificName; private readonly string _schema; + private readonly string _aliasedProc; private readonly Lazy _lazyParameters; - public Procedure(string name, string specificName, string schema) + public Procedure(string name, string specificName, string schema, string aliasedProc = "") { _name = name; _specificName = specificName; _schema = schema.NullIfWhitespace(); + _aliasedProc = aliasedProc; _lazyParameters = new Lazy(() => new ParameterCollection(GetParameters())); } - internal Procedure(string name, string specificName, string schema, DatabaseSchema databaseSchema) + internal Procedure(string name, string specificName, string schema, DatabaseSchema databaseSchema, string aliasedProc = "") { _name = name; _specificName = specificName; _schema = schema.NullIfWhitespace(); _lazyParameters = new Lazy(() => new ParameterCollection(GetParameters())); _databaseSchema = databaseSchema; + _aliasedProc = aliasedProc; } private IEnumerable GetParameters() @@ -52,6 +55,11 @@ public string Name get { return _name; } } + public string AliasedProcedure + { + get { return _aliasedProc; } + } + public ParameterCollection Parameters { get { return _lazyParameters.Value; } diff --git a/Simple.Data.Ado/Schema/Table.cs b/Simple.Data.Ado/Schema/Table.cs index a659dac3..74eb54e4 100644 --- a/Simple.Data.Ado/Schema/Table.cs +++ b/Simple.Data.Ado/Schema/Table.cs @@ -11,19 +11,21 @@ public class Table : IEquatable private readonly string _actualName; private readonly string _schema; private readonly TableType _type; + private readonly string _aliasedTable; private readonly DatabaseSchema _databaseSchema; private readonly Lazy _lazyColumns; private readonly Lazy _lazyPrimaryKey; private readonly Lazy _lazyForeignKeys; - public Table(string name, string schema, TableType type) + public Table(string name, string schema, TableType type, string aliasedTable = "") { _actualName = name; _schema = string.IsNullOrWhiteSpace(schema) ? null : schema; _type = type; + _aliasedTable = aliasedTable; } - internal Table(string name, string schema, TableType type, DatabaseSchema databaseSchema) + internal Table(string name, string schema, TableType type, DatabaseSchema databaseSchema, string aliasedTable = "") { _actualName = name; _databaseSchema = databaseSchema; @@ -32,6 +34,7 @@ internal Table(string name, string schema, TableType type, DatabaseSchema databa _lazyColumns = new Lazy(GetColumns); _lazyPrimaryKey = new Lazy(GetPrimaryKey); _lazyForeignKeys = new Lazy(GetForeignKeys); + _aliasedTable = aliasedTable; } public TableType Type @@ -74,6 +77,11 @@ public string QualifiedName } } + public string AliasedTable + { + get { return _aliasedTable; } + } + public IEnumerable Columns { get { return _lazyColumns.Value.AsEnumerable(); } @@ -195,7 +203,7 @@ public bool Equals(Table other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Equals(other._actualName, _actualName) && Equals(other._schema, _schema) && Equals(other._type, _type); + return Equals(other._actualName, _actualName) && Equals(other._schema, _schema) && Equals(other._type, _type) && Equals(other._aliasedTable, _aliasedTable); } /// diff --git a/Simple.Data.SqlServer/SqlSchemaProvider.cs b/Simple.Data.SqlServer/SqlSchemaProvider.cs index 9d022670..33448a2c 100644 --- a/Simple.Data.SqlServer/SqlSchemaProvider.cs +++ b/Simple.Data.SqlServer/SqlSchemaProvider.cs @@ -11,11 +11,13 @@ namespace Simple.Data.SqlServer class SqlSchemaProvider : ISchemaProvider { private readonly IConnectionProvider _connectionProvider; + private IEnumerable> _synonyms; public SqlSchemaProvider(IConnectionProvider connectionProvider) { if (connectionProvider == null) throw new ArgumentNullException("connectionProvider"); _connectionProvider = connectionProvider; + _synonyms = getSynonyms(); } public IConnectionProvider ConnectionProvider @@ -25,7 +27,19 @@ public IConnectionProvider ConnectionProvider public IEnumerable
GetTables() { - return GetSchema("TABLES").Select(SchemaRowToTable); + var tables = GetSchema("TABLES").Select(SchemaRowToTable); + //Add Synonyms a table to the tables list + List
synTables = new List
(); + foreach (var syn in _synonyms) + { + var tbl = tables.Where(t => + syn.Item2.Equals(string.Format("{0}.{1}", t.Schema, t.ActualName), StringComparison.CurrentCultureIgnoreCase)) + .FirstOrDefault(); + if (tbl != null) + synTables.Add(new Table(syn.Item1, tbl.Schema, tbl.Type, tbl.ActualName)); + } + + return tables.Union(synTables); } private static Table SchemaRowToTable(DataRow row) @@ -37,7 +51,11 @@ private static Table SchemaRowToTable(DataRow row) public IEnumerable GetColumns(Table table) { if (table == null) throw new ArgumentNullException("table"); - var cols = GetColumnsDataTable(table); + Table resolvedTable = table; + //if this table is a synonym, get columns from real table + if (!string.IsNullOrEmpty(table.AliasedTable)) + resolvedTable = new Table(table.AliasedTable, table.Schema, table.Type); + var cols = GetColumnsDataTable(resolvedTable); return cols.AsEnumerable().Select(row => SchemaRowToColumn(table, row)); } @@ -67,7 +85,19 @@ private static Column SchemaRowToColumn(Table table, DataRow row) public IEnumerable GetStoredProcedures() { - return GetSchema("Procedures").Select(SchemaRowToStoredProcedure); + var sprocs = GetSchema("Procedures").Select(SchemaRowToStoredProcedure); + //Add Synonyms a sproc to the sproc list + List synProcs = new List(); + foreach (var syn in _synonyms) + { + var proc = sprocs.Where(p => + syn.Item2.Equals(string.Format("{0}.{1}", p.Schema, p.SpecificName), StringComparison.CurrentCultureIgnoreCase)) + .FirstOrDefault(); + if (proc != null) + synProcs.Add(new Procedure(syn.Item1, syn.Item1, proc.Schema, proc.SpecificName)); + } + + return sprocs.Union(synProcs); } private IEnumerable GetSchema(string collectionName, params string[] constraints) @@ -87,6 +117,9 @@ private static Procedure SchemaRowToStoredProcedure(DataRow row) public IEnumerable GetParameters(Procedure storedProcedure) { + //support for aliased storec procedure names (synonyms) + var sprocName = string.IsNullOrEmpty(storedProcedure.AliasedProcedure) ? storedProcedure.QualifiedName + : (string.Format("[{0}].[{1}]", storedProcedure.Schema, storedProcedure.AliasedProcedure)); // GetSchema does not return the return value of e.g. a stored proc correctly, // i.e. there isn't sufficient information to correctly set up a stored proc. using (var connection = (SqlConnection)ConnectionProvider.CreateConnection()) @@ -94,7 +127,7 @@ public IEnumerable GetParameters(Procedure storedProcedure) using (var command = connection.CreateCommand()) { command.CommandType = CommandType.StoredProcedure; - command.CommandText = storedProcedure.QualifiedName; + command.CommandText = sprocName; connection.Open(); SqlCommandBuilder.DeriveParameters(command); @@ -109,7 +142,8 @@ public IEnumerable GetParameters(Procedure storedProcedure) public Key GetPrimaryKey(Table table) { if (table == null) throw new ArgumentNullException("table"); - var primaryKeys = GetPrimaryKeys(table.ActualName); + var tableName = string.IsNullOrEmpty(table.AliasedTable) ? table.ActualName : table.AliasedTable; + var primaryKeys = GetPrimaryKeys(tableName); if (primaryKeys == null) { return new Key(Enumerable.Empty()); @@ -117,7 +151,7 @@ public Key GetPrimaryKey(Table table) return new Key(primaryKeys.AsEnumerable() .Where( row => - row["TABLE_SCHEMA"].ToString() == table.Schema && row["TABLE_NAME"].ToString() == table.ActualName) + row["TABLE_SCHEMA"].ToString() == table.Schema && row["TABLE_NAME"].ToString() == tableName) .OrderBy(row => (int)row["ORDINAL_POSITION"]) .Select(row => row["COLUMN_NAME"].ToString())); } @@ -125,9 +159,10 @@ public Key GetPrimaryKey(Table table) public IEnumerable GetForeignKeys(Table table) { if (table == null) throw new ArgumentNullException("table"); - var groups = GetForeignKeys(table.ActualName) + var tableName = string.IsNullOrEmpty(table.AliasedTable) ? table.ActualName : table.AliasedTable; + var groups = GetForeignKeys(tableName) .Where(row => - row["TABLE_SCHEMA"].ToString() == table.Schema && row["TABLE_NAME"].ToString() == table.ActualName) + row["TABLE_SCHEMA"].ToString() == table.Schema && row["TABLE_NAME"].ToString() == tableName) .GroupBy(row => row["CONSTRAINT_NAME"].ToString()) .ToList(); @@ -162,7 +197,7 @@ public Type DataTypeToClrType(string dataType) private DataTable GetColumnsDataTable(Table table) { const string columnSelect = @"SELECT name, is_identity, type_name(system_type_id) as type_name, max_length from sys.columns -where object_id = object_id(@tableName, 'TABLE') or object_id = object_id(@tableName, 'VIEW') order by column_id"; + where object_id = object_id(@tableName, 'TABLE') or object_id = object_id(@tableName, 'VIEW') order by column_id"; var @tableName = new SqlParameter("@tableName", SqlDbType.NVarChar, 128); @tableName.Value = string.Format("[{0}].[{1}]", table.Schema, table.ActualName); return SelectToDataTable(columnSelect, @tableName); @@ -230,5 +265,37 @@ public String GetDefaultSchema() { return "dbo"; } + + private IEnumerable> getSynonyms() + { + const string synSelect = @"SELECT name, base_object_name FROM SYS.SYNONYMS"; + var dataTable = new DataTable(); + using (var cn = ConnectionProvider.CreateConnection() as SqlConnection) + { + using (var adapter = new SqlDataAdapter(synSelect, cn)) + { + adapter.Fill(dataTable); + } + + } + foreach (DataRow row in dataTable.Rows) + { + //get the object name with schema while removing the database name + var objName = buildBaseObjectName(row["base_object_name"].ToString()); + yield return new Tuple(row["name"].ToString(), objName); + } + } + + private string buildBaseObjectName(string baseObjectName) + { + + if (baseObjectName.Contains('.')) + { + var objParts = baseObjectName.Split('.'); + baseObjectName = objParts[objParts.Length - 2] + "." + objParts[objParts.Length - 1]; + } + + return baseObjectName.Replace("[", "").Replace("]", ""); + } } } From 7c7a082d9cb3af4039a864fc9b3ca86c185a197d Mon Sep 17 00:00:00 2001 From: Shelby Robertson Date: Mon, 25 Aug 2014 23:45:54 -0400 Subject: [PATCH 2/2] Second Attempt at synonyms, works properly for linked servers now. --- Simple.Data.Ado/ProcedureExecutor.cs | 2 +- .../Properties/Resources.Designer.cs | 34 +++- .../Properties/Resources.resx | 6 + .../Simple.Data.SqlServer.csproj | 6 + Simple.Data.SqlServer/SqlSchemaProvider.cs | 149 +++++++++++++----- 5 files changed, 155 insertions(+), 42 deletions(-) diff --git a/Simple.Data.Ado/ProcedureExecutor.cs b/Simple.Data.Ado/ProcedureExecutor.cs index e82670c7..5546e5d1 100644 --- a/Simple.Data.Ado/ProcedureExecutor.cs +++ b/Simple.Data.Ado/ProcedureExecutor.cs @@ -48,7 +48,7 @@ public IEnumerable Execute(IDictionary suppliedParame using (var command = cn.CreateCommand(_adapter.AdoOptions)) { command.Transaction = transaction; - command.CommandText = procedure.QualifiedName; + command.CommandText = string.IsNullOrEmpty(procedure.AliasedProcedure) ? procedure.QualifiedName : procedure.AliasedProcedure; command.CommandType = CommandType.StoredProcedure; SetParameters(procedure, command, suppliedParameters); try diff --git a/Simple.Data.SqlServer/Properties/Resources.Designer.cs b/Simple.Data.SqlServer/Properties/Resources.Designer.cs index c9ed52f6..811ddb82 100644 --- a/Simple.Data.SqlServer/Properties/Resources.Designer.cs +++ b/Simple.Data.SqlServer/Properties/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.1 +// Runtime Version:4.0.30319.18408 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -90,5 +90,37 @@ internal static string PrimaryKeySql { return ResourceManager.GetString("PrimaryKeySql", resourceCulture); } } + + /// + /// Looks up a localized string similar to select rc.CONSTRAINT_NAME, fkcu.TABLE_SCHEMA,'{0}' As TABLE_NAME, fkcu.COLUMN_NAME, + ///pkcu.TABLE_SCHEMA AS UNIQUE_TABLE_SCHEMA, pkcu.TABLE_NAME AS UNIQUE_TABLE_NAME, pkcu.COLUMN_NAME AS UNIQUE_COLUMN_NAME, + ///fkcu.ORDINAL_POSITION + ///FROM {1}INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc + ///JOIN {1}INFORMATION_SCHEMA.KEY_COLUMN_USAGE fkcu ON rc.CONSTRAINT_NAME = fkcu.CONSTRAINT_NAME + ///JOIN {1}INFORMATION_SCHEMA.KEY_COLUMN_USAGE pkcu ON rc.UNIQUE_CONSTRAINT_NAME = pkcu.CONSTRAINT_NAME + ///WHERE fkcu.ORDINAL_POSITION [rest of string was truncated]";. + /// + internal static string RemoteForeignKeysSql { + get { + return ResourceManager.GetString("RemoteForeignKeysSql", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to select kcu.TABLE_SCHEMA, '{0}' As TABLE_NAME, kcu.CONSTRAINT_NAME, tc.CONSTRAINT_TYPE, kcu.COLUMN_NAME, kcu.ORDINAL_POSITION + /// from {1}INFORMATION_SCHEMA.TABLE_CONSTRAINTS as tc + /// join {1}INFORMATION_SCHEMA.KEY_COLUMN_USAGE as kcu + /// on kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA + /// and kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME + /// and kcu.TABLE_SCHEMA = tc.TABLE_SCHEMA + /// and kcu.TABLE_NAME = tc.TABLE_NAME + /// where tc.CONSTRAINT_TYPE = 'PRIMARY KEY' + /// AND upper(kcu.table_name) = upper('{2}'). + /// + internal static string RemotePrimaryKeySql { + get { + return ResourceManager.GetString("RemotePrimaryKeySql", resourceCulture); + } + } } } diff --git a/Simple.Data.SqlServer/Properties/Resources.resx b/Simple.Data.SqlServer/Properties/Resources.resx index dea94da3..9d6a0ecc 100644 --- a/Simple.Data.SqlServer/Properties/Resources.resx +++ b/Simple.Data.SqlServer/Properties/Resources.resx @@ -124,4 +124,10 @@ ..\Resources\PrimaryKeySql.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + ..\Resources\RemoteForeignKeysSql.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + + ..\Resources\RemotePrimaryKeySql.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + \ No newline at end of file diff --git a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj index 87873058..c97aab71 100644 --- a/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj +++ b/Simple.Data.SqlServer/Simple.Data.SqlServer.csproj @@ -95,6 +95,12 @@ + + + + + +