Files
XLAB/XLAB2/ReferenceDirectorySqlHelpers.cs
Курнат Андрей 7bbca6ba55 edit
2026-03-22 21:44:29 +03:00

245 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
{
return LoadDeleteBlockersFromForeignKeys(connection, null, parentTableName, id, excludedChildTables);
}
public static List<DeleteBlockerInfo> LoadDeleteBlockersFromForeignKeys(SqlConnection connection, SqlTransaction transaction, string parentTableName, int id, IEnumerable<string> excludedChildTables = null)
{
var excluded = new HashSet<string>(excludedChildTables ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase);
var metadata = LoadForeignKeyMetadata(connection, transaction, 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.Transaction = transaction;
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, SqlTransaction transaction, 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.Transaction = transaction;
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; }
}
}
}