Как создать дату рождения в БД и ОРМ для объединения известных и неизвестных частей даты
Запишите вверх, мой вопрос окажется похожим на вопрос SO 1668172.
Это вопрос дизайна, который наверняка должен был появиться для других, но я не смог найти ответ, который соответствует моей ситуации. Я хочу записать дату рождения в моем приложении с несколькими "уровнями" информации:
-
NULL
значение, то есть DoB не указано
-
1950-??-??
Известно только значение года DoB, дата/месяц не
-
????-11-23
Всего месяц, день или комбинация из двух, но без года
-
1950-11-23
Известен полный DoB
Технологии, которые я использую для моего приложения, следующие:
- Asp.NET 4 (С#), возможно, с MVC
- Некоторые решения ORM, возможно, Linq-to-sql или NHibernate's
- MSSQL Server 2008, сначала только версия Express
Возможности для SQL-бит, которые до сих пор переходили мне в голову:
- 1) Используйте один нулевой столбец varchar, например.
1950-11-23
, и замените unkowns на "X", например. XXXX-11-23
или 1950-XX-XX
- 2) Используйте три нулевых столбца int, например.
1950
, 11
и 23
- 3) Используйте столбец INT за год, а также столбец datetime для всех известных DoBs
Для конца С# этой проблемы я просто воспользовался этими двумя вариантами:
- A) Используйте свойство string для представления DoB, конвертируйте только для просмотра.
- B) Используйте пользовательскую (?) структуру или класс для DoB с тремя целыми числами с нулевым значением.
- C) Используйте нулевое значение DateTime вместе с нулевым целым числом в течение года
Решения, похоже, образуют согласованные пары в 1A, 2B или 3C. Конечно, 1A не является хорошим решением, но оно устанавливает базовую линию.
Любые советы и ссылки высоко ценятся. Ну, если они связаны, так или иначе:)
Изменить, об ответах. Я принял один ответ как принятый, потому что я думаю, что это сработает для меня. Это стоит посмотреть на другие ответы, хотя, если вы споткнулись здесь с тем же вопросом.
Ответы
Ответ 1
Сторона SQL
Моя последняя идея по этому вопросу - использовать диапазон для неопределенных дат или может иметь различную специфику. Для двух столбцов:
DobFromDate (inclusive)
DobToDate (exclusive)
Вот как это будет работать с вашими сценариями:
Specificity DobFromDate DobToDate
----------- ----------- ----------
YMD 2006-05-05 2006-05-06
YM 2006-05-01 2006-06-01
Y 2006-01-01 2007-01-01
Unknown 0000-01-01 9999-12-31
-> MD, M, D not supported with this scheme
Обратите внимание, что нет причин, по которым это невозможно выполнить до часа, минуты, секунды, миллисекунды и т.д.
Затем при запросе для людей, родившихся в определенный день:
DECLARE @BornOnDay date = '2006-05-16'
-- Include lower specificity:
SELECT *
FROM TheTable
WHERE
DobFromDate <= @BornOnDay
AND @BornOnDay < DobToDate;
-- Exclude lower specificity:
SELECT *
FROM TheTable
WHERE
DobFromDate = @BornOnDay
AND DobToDate = DateAdd(Day, 1, @BornOnDay);
Это для меня самое лучшее сочетание ремонтопригодности, простоты использования и выразительной мощности. Он не будет обрабатывать потерю точности в более значимых значениях (например, вы знаете месяц и день, но не год), но если это можно обойти, я думаю, что это победитель.
Если вы когда-либо будете запрашивать дату, тогда в целом лучшие решения (на мой взгляд) будут теми, которые сохраняют элементы как даты на сервере определенным образом.
Также обратите внимание, что если вы ищете диапазон дат, а не один день, с моим решением вам все еще нужны только два условия, а не четыре:
DECLARE
@FromBornOnDay date = '2006-05-16',
@ToBornOnDay date = '2006-05-23';
-- Include lower specificity:
SELECT *
FROM TheTable
WHERE
DobFromDate < @ToBornOnDay
AND @FromBornOnDay < DobToDate;
Сторона С#
Я бы использовал пользовательский класс со всеми методами, необходимыми для правильной сопоставления даты и даты на нем. Вы знаете требования к бизнесу в отношении того, как вы будете использовать неизвестные даты, и можете кодировать логику внутри класса. Если вам нужно что-то до определенной даты, вы будете использовать только известные или неизвестные предметы? Что вернет ToString()
? Это, на мой взгляд, лучше всего решать с помощью класса.
Ответ 2
Мне нравится идея 3 int nullable столбцов и структура из 3 нулевых int в С#.
для обработки db требуется некоторое усилие, но вы можете избежать синтаксического анализа вокруг строк, и вы также можете запросить SQL-запрос непосредственно по году или году и месяцу и так далее...
Ответ 3
Все, что вы делаете, будет грязным DB. Для потребителей таких дат я бы написал специальный класс/структуру, который инкапсулирует, какую дату он (я бы назвал его чем-то вроде PartialDate), чтобы облегчить дело для потребителей - как Мартин Фаулер защищает "Деньги" .
Если вы обнаружите DateTime непосредственно на С#, это может привести к путанице, если у вас была "дата"???? -11-23, и вы хотели определить, был ли клиент старше 18 лет, как бы вы по умолчанию не указали дату, как потребитель узнает, что часть даты была недействительной и т.д.?
Дополнительным преимуществом использования PartialDate является то, что другие люди, читающие ваш код, быстро поймут, что они не являются нормальными, полными датами и не должны рассматриваться как таковые!
Edit
Размышляя о концепции частичных данных, я решил использовать Google. Я обнаружил, что существует понятие Partial on Joda time и интересный PDF файл по теме, которая может быть или не быть полезной для вас.
Ответ 4
Интересная проблема...
Мне нравится решение 2B над решением 3C, потому что с 3C оно не будет нормализовано... когда вы обновляете один из int, вам также придется обновлять DateTime, иначе вы бы не синхронизировались.
Однако, когда вы читаете данные в своем конце С#, у меня будет свойство, которое сверлит все ints в строку, отформатированную так, как вы в решении 1, чтобы ее можно было легко отобразить.
Мне любопытно, какой тип отчетов вам нужно будет делать с этими данными... или если вы просто будете хранить и извлекать его из базы данных.
Ответ 5
Я бы не стал беспокоиться о том, как хранить дату, но я бы сохранил дату в поле datetime, НО, если зная, что какая-то часть даты не была заполнена, у меня будут флаги для каждого раздела дата, которая недействительна, поэтому ваша схема будет:
DBODate как дата
DayIsSet как бит
MonthIsSet как бит
YearIsSet как бит.
Таким образом, вы все равно можете реализовать все допустимые сопоставления дат и все еще знать точность даты, над которой работаете. (что касается даты, я бы всегда по умолчанию считал недостающую часть как мин этого значения: IE Месяц по умолчанию - январь, день - первый, год - 1900 или что-то еще).
Ответ 6
Очевидно, что все упомянутые выше решения действительно представляют собой компромисс.
Поэтому я бы рекомендовал тщательно подумать, какой из "уровней" является наиболее вероятным и оптимизировать для этого. Затем перейдите для правильной обработки исключений для других редких случаев.
Я не знаю, является ли отчетность проблемой для вас прямо сейчас или может быть позже, но вы можете считать это третьим аспектом помимо проблем с DB/С#.