This commit is contained in:
Курнат Андрей
2026-03-19 23:31:41 +03:00
parent ce3a3f02d2
commit a47a7a5a3b
104 changed files with 21982 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
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; }
}
}
}