Вопрос или проблема
У меня есть большой словарь (с более чем 1000 записей), определенный следующим образом:
Dictionary<string, string> dic = new Dictionary<string, string>
{
...
{ PlanVariablesDefinitions.Growth_Proposed_Savings_Rate, L.GrowthProposedSavingsRate.ToString("0.00") },
{ PlanVariablesDefinitions.Growth_Proposed_Income_Growth_Rate, L.GrowthProposedIncomeGrowthRate.ToString() },
{ PlanVariablesDefinitions.Growth_Proposed_Portfolio_Growth_Rate, L.GrowthProposedPortfolioGrowthRate.ToString() },
{ PlanVariablesDefinitions.Growth_Proposed_FMVRealEstate_Growth_Rate, L.GrowthProposedFMVRealEstateGrowthRate.ToString() },
...
}
А константы определены следующим образом:
...
public const string Growth_Proposed_Savings_Rate = "Growth_Proposed_Savings_Rate";
public const string Growth_Proposed_Income_Growth_Rate = "Growth_Proposed_Income_Growth_Rate";
public const string Growth_Proposed_Portfolio_Growth_Rate = "Growth_Proposed_Portfolio_Growth_Rate";
public const string Growth_Proposed_FMVRealEstate_Growth_Rate = "Growth_Proposed_FMVRealEstate_Growth_Rate";
...
Этот словарь сохраняется в базе данных SQL в некоторых столбцах, где длина установлена как nvarchar(50)
для каждого. У меня есть ситуации, когда из-за рефакторинга (чтобы дать значимое описание) значение константы меняется на что-то длиннее 50 символов, например, с “Growth_Proposed_FMVRealEstate_Growth_Rate” на “Growth_Proposed_FMVRealEstate_Growth_Rate_from_2023_2024_First_Quater”. Когда это изменение выполняется, разработчик не осознает, что эта модификация приведет к сбою системы в какой-то момент, когда эта строка будет вставлена в БД.
На данный момент я просто вставил этот код, чтобы быстро вызвать сбой и обнаружить такие случаи при первом запуске:
foreach (var item in dic)
if (item.Key.Length > 50)
throw new Exception($"{item.Key} превышает 50 символов и вызовет исключение при сохранении в БД.");
Мой вопрос в том, могу ли я предотвратить такие ситуации на этапе компиляции с помощью каких-либо атрибутов? Или есть ли какое-либо другое действительное решение, чтобы избежать этого обходного пути?
На этапе компиляции действительно нет никаких вариантов, кроме как запуска внешнего инструмента/дополнения для валидации. Учтите, что внешняя валидация будет работать для таких вещей, как сгенерированные константы, но не для присвоений во время выполнения из строк переменных. Во время выполнения вы можете интегрировать проверку фиксированной длины в пользовательский тип, который утверждает свою длину:
public abstract class StringN(string value, uint length)
{
public string Value { get; } = value.LengthChecked(length);
public override string ToString()
{
return Value;
}
public static implicit operator string(StringN value)
{
return value.Value;
}
}
public sealed class String50(string value) : StringN(value, Length)
{
public const uint Length = 50;
public static implicit operator String50(string value)
{
return new String50(value);
}
}
public static class FixedLengthStringExtensions
{
public static string LengthChecked(this string value, uint length)
{
if (!string.IsNullOrEmpty(value) && value.Length > length)
throw new ArgumentException($"Длина фиксированной строки превышена. '{value}' {value.Length}/{length}");
return value;
}
}
Это будет проверять и вызывать исключение во время выполнения, когда используется словарь или любое другое использование String{N}. В вашем случае единственным изменением будет объявление:
Dictionary<String50, string> dic = new Dictionary<String50, string>
{
...
{ PlanVariablesDefinitions.Growth_Proposed_Savings_Rate, L.GrowthProposedSavingsRate.ToString("0.00") },
{ PlanVariablesDefinitions.Growth_Proposed_Income_Growth_Rate, L.GrowthProposedIncomeGrowthRate.ToString() },
{ PlanVariablesDefinitions.Growth_Proposed_Portfolio_Growth_Rate, L.GrowthProposedPortfolioGrowthRate.ToString() },
{ PlanVariablesDefinitions.Growth_Proposed_FMVRealEstate_Growth_Rate, L.GrowthProposedFMVRealEstateGrowthRate.ToString() },
...
}
Это довольно компактно. Я выбрал валидацию длины как метод расширения, который, как мне кажется, довольно хорошо вписывается в первичную инициализацию конструктора.
Нет необходимости писать цикл предварительной валидации, и он может поймать любые ситуации во время выполнения, когда фиксированное значение фиксированной длины переназначается. Вам нужно будет объявить класс для каждой/любой фиксированной длины строки, которую вы хотите использовать, но это очень просто с просто обновлением имени и постоянной длиной. Присвоение из/в string
реализовано и готово к использованию.
Вдохновение из этого ответа: Строковый объект с фиксированной длиной C#
.
Ответ или решение
Как предотвратить сохранение сущностей Entity Framework, когда длина строк превышает длину столбца в базе данных
В процессе работы с базами данных, особенно при использовании Entity Framework, очень важно обеспечить целостность данных. Одной из распространенных проблем, с которой могут столкнуться разработчики, является ситуация, когда строка превышает установленное ограничение длины столбца в базе данных. В данном случае рассматривается решение для предотвращения сохранения строк, превышающих длину nvarchar(50)
, и осуществляется анализ подходов к решению данной проблемы.
Проблема и её последствия
Ситуация, описанная вами, может привести к серьезным ошибкам во время выполнения приложения, особенно если кто-то изменит значение ключа в словаре, увеличив его длину. Если длина строки превышает 50 символов, то при попытке сохранить такую сущность в базе данных будет выброшено исключение, что не только ухудшает пользовательский опыт, но и может вызвать проблемы с целостностью данных.
Текущая реализация, которая выбрасывает исключение, если длина строки превышает 50 символов, является временной мерой. Для более надежного решения можно разработать систему, которая обеспечит проверку длины на этапе компиляции или, по крайней мере, на этапе выполнения.
Программное решение
Для предотвращения таких ситуаций в будущем рекомендуется использовать специальный тип данных, который будет автоматически проверять длину строк при их инициализации. Ниже приведено примерное решение на языке C#.
Реализация кастомного типа StringN
public abstract class StringN
{
public string Value { get; }
protected StringN(string value, uint length)
{
Value = LengthChecked(value, length);
}
public override string ToString() => Value;
public static implicit operator string(StringN value) => value.Value;
protected static string LengthChecked(string value, uint length)
{
if (!string.IsNullOrEmpty(value) && value.Length > length)
throw new ArgumentException($"Длина строки превышает допустимую. '{value}' {value.Length}/{length}");
return value;
}
}
public sealed class String50 : StringN
{
private const uint Length = 50;
public String50(string value) : base(value, Length) { }
public static implicit operator String50(string value) => new String50(value);
}
Применение в словаре
После создания кастомного типа String50
, вы можете использовать его в вашем словаре:
Dictionary<String50, string> dic = new Dictionary<String50, string>
{
{ new String50(PlanVariablesDefinitions.Growth_Proposed_Savings_Rate), L.GrowthProposedSavingsRate.ToString("0.00") },
{ new String50(PlanVariablesDefinitions.Growth_Proposed_Income_Growth_Rate), L.GrowthProposedIncomeGrowthRate.ToString() },
{ new String50(PlanVariablesDefinitions.Growth_Proposed_Portfolio_Growth_Rate), L.GrowthProposedPortfolioGrowthRate.ToString() },
{ new String50(PlanVariablesDefinitions.Growth_Proposed_FMVRealEstate_Growth_Rate), L.GrowthProposedFMVRealEstateGrowthRate.ToString() },
};
Заключение
Использование кастомного типа для строк с фиксированной длиной поможет предотвратить случайное сохранение значений, превышающих длину столбца базы данных. Это обеспечивает более строгую проверку на этапе выполнения, тем самым минимизируя вероятность возникновения ошибок, которые могут оказать негативное влияние на приложение.
Для достижения более высокого уровня надежности и безопасности данных, рекомендуется следить за длиной строк в базах данных и использовать описанный выше подход в проектировании структуры данных. Регулярные код-ревью и автоматизированные тесты также будут полезны для выявления потенциальных проблем с длиной строк на ранней стадии разработки.