using System; using System.Collections.Generic; using System.Data; using Microsoft.Data.SqlClient; using System.Linq; using XLAB2.Infrastructure; namespace XLAB2 { internal sealed class DeleteBlockerInfo { public int RowCount { get; set; } public string TableName { get; set; } } internal static class ReferenceDirectorySqlHelpers { public static SqlConnection CreateConnection() { return SqlServerConnectionFactory.Current.CreateConnection(); } public static int GetCommandTimeoutSeconds() { return SqlServerConnectionFactory.Current.Options.CommandTimeoutSeconds; } public static IReadOnlyList LoadLookupItems(string sql) { var items = new List(); using (var connection = CreateConnection()) using (var command = new SqlCommand(sql, connection)) { connection.Open(); command.CommandTimeout = GetCommandTimeoutSeconds(); using (var reader = command.ExecuteReader()) { while (reader.Read()) { items.Add(new DirectoryLookupItem { Id = GetInt32(reader, "Id"), Name = GetString(reader, "Name") }); } } } return items; } public static List LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable excludedChildTables = null) { var excluded = new HashSet(excludedChildTables ?? Enumerable.Empty(), StringComparer.OrdinalIgnoreCase); var metadata = LoadForeignKeyMetadata(connection, parentTableName) .Where(delegate(ForeignKeyMetadata item) { return !excluded.Contains(item.TableName); }) .GroupBy(delegate(ForeignKeyMetadata item) { return item.SchemaName + "." + item.TableName; }) .Select(delegate(IGrouping group) { var first = group.First(); return new { first.SchemaName, first.TableName, Columns = group.Select(delegate(ForeignKeyMetadata item) { return item.ColumnName; }).Distinct(StringComparer.OrdinalIgnoreCase).ToList() }; }) .ToList(); var blockers = new List(); foreach (var item in metadata) { var whereClause = string.Join(" OR ", item.Columns.Select(delegate(string columnName) { return string.Format("[{0}] = @Id", EscapeIdentifier(columnName)); })); var sql = string.Format( "SELECT COUNT(*) FROM [{0}].[{1}] WHERE {2};", EscapeIdentifier(item.SchemaName), EscapeIdentifier(item.TableName), whereClause); using (var command = new SqlCommand(sql, connection)) { command.CommandTimeout = GetCommandTimeoutSeconds(); command.Parameters.Add("@Id", SqlDbType.Int).Value = id; var rowCount = Convert.ToInt32(command.ExecuteScalar()); if (rowCount > 0) { blockers.Add(new DeleteBlockerInfo { TableName = item.TableName, RowCount = rowCount }); } } } return blockers.OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }).ToList(); } public static string CreateDeleteBlockedMessage(string tableName, IEnumerable blockers) { var details = string.Join(", ", (blockers ?? Enumerable.Empty()) .OrderBy(delegate(DeleteBlockerInfo blocker) { return blocker.TableName; }) .Select(delegate(DeleteBlockerInfo blocker) { return string.Format("{0}: {1}", blocker.TableName, blocker.RowCount); })); return string.Format( "Запись {0} не может быть удалена, потому что на неё есть ссылки в таблицах: {1}.", tableName, string.IsNullOrWhiteSpace(details) ? "связанные данные" : details); } public static string CreateDeleteBlockedMessage(string tableName, SqlException ex) { var suffix = ex == null || string.IsNullOrWhiteSpace(ex.Message) ? string.Empty : " " + ex.Message.Trim(); return string.Format( "Запись {0} не может быть удалена из-за ограничений ссылочной целостности в БД.{1}", tableName, suffix); } public static bool IsDuplicateViolation(SqlException ex, string indexName) { return ex != null && (ex.Number == 2601 || ex.Number == 2627) && ex.Message.IndexOf(indexName, StringComparison.OrdinalIgnoreCase) >= 0; } public static int GetInt32(SqlDataReader reader, string columnName) { return reader.GetInt32(reader.GetOrdinal(columnName)); } public static int? GetNullableInt32(SqlDataReader reader, string columnName) { var ordinal = reader.GetOrdinal(columnName); return reader.IsDBNull(ordinal) ? (int?)null : reader.GetInt32(ordinal); } public static string GetString(SqlDataReader reader, string columnName) { var ordinal = reader.GetOrdinal(columnName); return reader.IsDBNull(ordinal) ? string.Empty : Convert.ToString(reader.GetValue(ordinal)); } public static DateTime? GetNullableDateTime(SqlDataReader reader, string columnName) { var ordinal = reader.GetOrdinal(columnName); return reader.IsDBNull(ordinal) ? (DateTime?)null : reader.GetDateTime(ordinal); } public static bool? GetNullableBoolean(SqlDataReader reader, string columnName) { var ordinal = reader.GetOrdinal(columnName); return reader.IsDBNull(ordinal) ? (bool?)null : reader.GetBoolean(ordinal); } public static void AddNullableIntParameter(SqlCommand command, string name, int? value) { command.Parameters.Add(name, SqlDbType.Int).Value = (object)value ?? DBNull.Value; } public static void AddNullableStringParameter(SqlCommand command, string name, SqlDbType dbType, int size, string value) { command.Parameters.Add(name, dbType, size).Value = (object)value ?? DBNull.Value; } public static void AddNullableDateTimeParameter(SqlCommand command, string name, DateTime? value) { command.Parameters.Add(name, SqlDbType.DateTime).Value = (object)value ?? DBNull.Value; } public static void AddNullableTextParameter(SqlCommand command, string name, string value) { command.Parameters.Add(name, SqlDbType.VarChar, -1).Value = (object)value ?? DBNull.Value; } private static IReadOnlyList LoadForeignKeyMetadata(SqlConnection connection, string parentTableName) { const string sql = @" SELECT OBJECT_SCHEMA_NAME(fk.parent_object_id) AS SchemaName, OBJECT_NAME(fk.parent_object_id) AS TableName, childColumn.name AS ColumnName FROM sys.foreign_keys fk JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id JOIN sys.columns childColumn ON childColumn.object_id = fkc.parent_object_id AND childColumn.column_id = fkc.parent_column_id WHERE OBJECT_NAME(fk.referenced_object_id) = @ParentTableName ORDER BY TableName, ColumnName;"; var result = new List(); using (var command = new SqlCommand(sql, connection)) { command.CommandTimeout = GetCommandTimeoutSeconds(); command.Parameters.Add("@ParentTableName", SqlDbType.VarChar, 128).Value = parentTableName; using (var reader = command.ExecuteReader()) { while (reader.Read()) { result.Add(new ForeignKeyMetadata { SchemaName = GetString(reader, "SchemaName"), TableName = GetString(reader, "TableName"), ColumnName = GetString(reader, "ColumnName") }); } } } return result; } private static string EscapeIdentifier(string identifier) { return (identifier ?? string.Empty).Replace("]", "]]"); } private sealed class ForeignKeyMetadata { public string ColumnName { get; set; } public string SchemaName { get; set; } public string TableName { get; set; } } } }