Это хорошая идея создать собственный тип для первичного ключа каждой таблицы данных?

У нас есть много кода, который передает о "Идентификаторах" строк данных; это в основном ints или guids. Я могу сделать этот код более безопасным, создав другую структуру для идентификатора каждой таблицы базы данных. Затем средство проверки типов поможет найти случаи, когда передается неправильный идентификатор.

Например, таблица Person имеет столбец, вызывающий PersonId, и у нас есть код типа:

DeletePerson(int personId)
DeleteCar(int carId)

Было бы лучше иметь:

struct PersonId
{
   private int id;
   // GetHashCode etc....
}

DeletePerson(PersionId persionId)
DeleteCar(CarId carId)
  • Кто-нибудь получил реальный жизненный опыт dong это?

  • Стоит ли накладные расходы?

  • Или больше боли, чем это стоит?

(Это также облегчило бы изменение типа данных в базе данных первичного ключа, то есть, в первую очередь, я думал об этом идеале)


Пожалуйста, не говорите, используйте ORM какое-то другое большое изменение в дизайне системы, поскольку я знаю, что ORM будет лучшим вариантом, но в настоящее время это не под моей властью. Однако я могу внести незначительные изменения, как указано выше, в модуль, над которым я сейчас работаю.

Update: Обратите внимание, что это не веб-приложение, и идентификаторы хранятся в памяти и передаются с WCF, поэтому нет никакого преобразования в/из строк на краю. Нет никакой причины, по которой интерфейс WCF не может использовать тип PersonId и т.д. Тип PeopleId и т.д. Может даже использоваться в коде интерфейса WPF/Winforms.

Единственным по своей сути "нетипизированным" битом системы является база данных.


Похоже, что это связано с затратами/затратами на время написания кода, который компилятор может проверить лучше, или потратить время на создание дополнительных модульных тестов. Я больше склоняюсь к тому, чтобы тратить время на тестирование, поскольку мне хотелось бы увидеть хотя бы некоторые модульные тесты в базе кода.

Ответ 1

Я бы не сделал специальный идентификатор для этого. Это в основном проблема тестирования. Вы можете проверить код и убедиться, что он делает то, что он должен.

Вы можете создать стандартный способ делать вещи в своей системе, чем помогать будущему обслуживанию (аналогично тому, что вы упоминаете), передавая весь объект, который нужно манипулировать. Конечно, если вы назвали свой параметр (int personID) и имели документацию, то любой не вредоносный программист должен иметь возможность эффективно использовать этот код при вызове этого метода. Передача целого объекта будет соответствовать тому типу, который вы ищете, и этого должно быть достаточно стандартизованным способом.

Я просто вижу, что у вас есть специальная структура, которая защищает от этого, добавляя больше работы для небольшой пользы. Даже если вы это сделаете, кто-то может прийти и найти удобный способ сделать "вспомогательный" метод и обойти любую структуру, которую вы положили на место, так что это действительно не гарантия.

Ответ 2

Трудно понять, как это может быть полезно: я рекомендую делать это только в крайнем случае, и только если люди на самом деле смешивают идентификаторы во время разработки или сообщают о трудностях с их сохранением.

В веб-приложениях, в частности, он даже не будет предлагать безопасность, на которую вы надеетесь: обычно вы будете преобразовывать строки в целые числа. Слишком много случаев, когда вы обнаружите, что пишете такой глупый код:

int personId;
if (Int32.TryParse(Request["personId"], out personId)) { 
    this.person = this.PersonRepository.Get(new PersonId(personId));
}

Работа с сложным состоянием в памяти, безусловно, улучшает дело для сильно типизированных идентификаторов, но я думаю, идея Артура еще лучше: во избежание путаницы требовать экземпляр сущности вместо идентификатора. В некоторых ситуациях соображения производительности и памяти могут сделать это непрактичным, но даже это должно быть достаточно редким, чтобы обзор кода был столь же эффективным без отрицательных побочных эффектов (совсем наоборот!).

Я работал над системой, которая сделала это, и это действительно не придавало значения. У нас не было двусмысленностей, подобных тем, которые вы описываете, и с точки зрения будущей проверки было немного сложнее реализовать новые функции без каких-либо выплат. (Тип данных ID не изменился за два года, во всяком случае, это может произойти в какой-то момент, но, насколько мне известно, возврат инвестиций для этого в настоящее время отрицательный.)

Ответ 3

Вы можете просто выбрать GUID, как вы сами предложили. Тогда вам не придется беспокоиться о передаче идентификатора человека "42" на DeleteCar() и случайного удаления автомобиля с идентификатором 42. GUID уникальны; если вы передадите GUID человека в DeleteCar в свой код из-за опечатки программирования, этот GUID не будет PK любого автомобиля в базе данных.

Ответ 4

Вы можете создать простой класс Id, который может помочь различать код между двумя:

public class Id<T>
{
    private int RawValue
    {
        get;
        set;
    }

    public Id(int value)
    {
        this.RawValue = value;
    }

    public static explicit operator int (Id<T> id) { return id.RawValue; }

    // this cast is optional and can be excluded for further strictness
    public static implicit operator Id<T> (int value) { return new Id(value); }
}

Используется так:

class SomeClass
{
     public Id<Person> PersonId { get; set; }
     public Id<Car> CarId { get; set; }
}

Предполагая, что ваши значения будут извлекаться только из базы данных, если вы явно не передали значение целому числу, невозможно использовать их в другом месте.

Ответ 5

Я не вижу большого значения в пользовательской проверке в этом случае. Возможно, вы захотите расширить свой тестовый набор, чтобы проверить, что происходит две вещи:

  • Ваш код доступа к данным всегда работает так, как вы ожидаете (т.е. вы не загружаете несогласованную информацию о ключах в свои классы и получаете из-за этого неправильное использование).
  • Что ваш код "туда и обратно" работает так, как ожидалось (т.е. что загрузка записи, внесение изменений и сохранение ее обратно не как-то искажает ваши бизнес-логические объекты).

Наличие уровня доступа к данным (и бизнес-логики), которому вы можете доверять, имеет решающее значение для решения проблем с большими картинками, с которыми вы столкнетесь с попыткой реализовать реальные бизнес-требования. Если ваш уровень данных ненадежен, вы будете тратить много усилий на отслеживание (или, что еще хуже, на работу) проблем на том уровне, который будет отображаться при загрузке подсистемы.

Если вместо этого ваш код доступа к данным является надежным перед лицом неправильного использования (то, что ваш тестовый набор должен доказать вам), тогда вы можете немного расслабиться на более высоких уровнях и доверять тому, что они будут генерировать исключения (или, тем не менее, вы имеете дело с ним) при злоупотреблении.

Причина, по которой вы слышите людей, предлагающих ORM, заключается в том, что многие из этих проблем решаются надежными способами с помощью таких инструментов. Если ваша реализация достаточно далека от того, что такой коммутатор будет болезненным, просто имейте в виду, что ваш уровень доступа к данным низкого уровня должен быть таким же надежным, как хороший ОРМ, если вы действительно хотите быть в состоянии доверять (и, таким образом, забыть о в определенной степени) ваш доступ к данным.

Вместо выборочной проверки ваш тестовый пакет может вводить код (через инъекцию зависимостей), который выполняет надежные тесты ваших ключей (попадание в базу данных для проверки каждого изменения) по мере запуска тестов и инъекции производственного кода, который исключает или ограничивает такие тесты по соображениям производительности. Ваш уровень данных будет вызывать ошибки при неудачных ключах (если у вас есть ваши внешние ключи, настроенные правильно там), поэтому вы также сможете обрабатывать эти исключения.

Ответ 6

Моя кишка говорит, что это просто не стоит хлопот. Мой первый вопрос для вас будет заключаться в том, действительно ли вы обнаружили ошибки, когда был передан неправильный int (идентификатор автомобиля вместо идентификатора лица в вашем примере). Если это так, вероятно, это скорее худшая общая архитектура в том, что объекты вашего домена имеют слишком много связей и передают слишком много аргументов в параметрах метода, а не действуют на внутренние переменные.