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

У нас много кода, который передает около « Ids » строк данных;в основном это целые или направляющие.Я мог бы сделать этот код более безопасным, создав другую структуру для идентификатора каждой таблицы базы данных. Тогда средство проверки типов поможет найти случаи, когда передан неправильный идентификатор.

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

DeletePerson(int personId)
DeleteCar(int carId)

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

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

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

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

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

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


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

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

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


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

Ответы [ 6 ]

4 голосов
/ 11 января 2010

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

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

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

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

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

2 голосов
/ 11 января 2010

Вы можете создать простой класс 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; }
}

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

2 голосов
/ 11 января 2010

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

2 голосов
/ 11 января 2010

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

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

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

1 голос
/ 11 января 2010

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

1 голос
/ 11 января 2010

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...