238 lines
9.7 KiB
C#
238 lines
9.7 KiB
C#
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<DirectoryLookupItem> LoadLookupItems(string sql)
|
||
{
|
||
var items = new List<DirectoryLookupItem>();
|
||
|
||
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<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
|
||
{
|
||
var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), 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<string, ForeignKeyMetadata> 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<DeleteBlockerInfo>();
|
||
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<DeleteBlockerInfo> blockers)
|
||
{
|
||
var details = string.Join(", ", (blockers ?? Enumerable.Empty<DeleteBlockerInfo>())
|
||
.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<ForeignKeyMetadata> 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<ForeignKeyMetadata>();
|
||
|
||
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; }
|
||
}
|
||
}
|
||
}
|
||
|