пятница, 15 августа 2008 г.

Миграция данных из одной таблицы в другую. Гибкое решение.

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

  1. string nsvid="";
  2. string nsit="";
  3. string type="";
  4. string nzav="";
  5. string ninv="";
  6. string lim="";
  7. string tochnost="";
  8. string where="";
  9. System.DateTime dtp = System.DateTime.Today;
  10. Int64 pov=0;
  11. System.DateTime dtn = System.DateTime.Today;
  12. string texsos="";
  13. for (Int64 i = 0; i <>
  14. {
  15. if (!bdt[int.Parse(i.ToString())].Is___п_пNull())
  16. pp = long.Parse(bdt[int.Parse(i.ToString())].___п_п.ToString());
  17. if (!bdt[int.Parse(i.ToString())].Is___свид_Null())
  18. nsvid = bdt[int.Parse(i.ToString())].___свид_;
  19. if (!bdt[int.Parse(i.ToString())].IsНаименование_СИТNull())
  20. nsit = bdt[int.Parse(i.ToString())].Наименование_СИТ;
  21. if (!bdt[int.Parse(i.ToString())].IsТипNull())
  22. type = bdt[int.Parse(i.ToString())].Тип;
  23. if (!bdt[int.Parse(i.ToString())].Is_Заводской__Null())
  24. nzav = bdt[int.Parse(i.ToString())]._Заводской__;
  25. if (!bdt[int.Parse(i.ToString())].Is_Инвентарный___Null())
  26. ninv = bdt[int.Parse(i.ToString())]._Инвентарный___;
  27. if (!bdt[int.Parse(i.ToString())].IsПредел_измеренийNull())
  28. lim = bdt[int.Parse(i.ToString())].Предел_измерений;
  29. if (!bdt[int.Parse(i.ToString())].Is_Класс__разряд__ц_д___погрешностьNull())
  30. tochnost = bdt[int.Parse(i.ToString())]._Класс__разряд__ц_д___погрешность;
  31. if (!bdt[int.Parse(i.ToString())].IsВладелецNull())
  32. where = bdt[int.Parse(i.ToString())].Владелец;
  33. if (!bdt[int.Parse(i.ToString())].IsДата_поверкиNull())
  34. dtp = bdt[int.Parse(i.ToString())].Дата_поверки;
  35. if (!bdt[int.Parse(i.ToString())].IsПериодичность_поверкиNull())
  36. pov = long.Parse(bdt[int.Parse(i.ToString())].Периодичность_поверки.ToString());
  37. if (!bdt[int.Parse(i.ToString())].IsДата_следующей_поверкиNull())
  38. dtn = bdt[int.Parse(i.ToString())].Дата_следующей_поверки;
  39. if (!bdt[int.Parse(i.ToString())].Is_Тех_состояниеNull())
  40. texsos = bdt[int.Parse(i.ToString())]._Тех_состояние;
  41. pdt.AddPriborRow(i+1, nsvid, nsit, type, nzav, ninv, lim, tochnost, where, dtp, pov, dtn, tochnost);
  42. }
  43. priborTableAdapter.Update(pdt);
  44. pdt.AcceptChanges();


Достаточно беглого взгляда на этот код чтобы понять - реализация метода неоптимальна.

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

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

1. Делаем универсальный конвертер данных:

  1. public static class ConvertUtils
  2. {
  3. public static object ToType(object originalValue, Type targetType)
  4. {
  5. if (targetType == typeof(string))
  6. {
  7. return originalValue.ToString();
  8. }
  9. if (targetType == typeof(int))
  10. {
  11. return int.Parse(originalValue.ToString());
  12. }
  13. throw new Exception(
  14. string.Format(
  15. "Type {0} is not expected.",
  16. originalValue.GetType().FullName));
  17. }
  18. }


Если есть необходимость можно расширить метод до ToType(object originalValue, Type originalType, Type targetType)

2. Заводим "словарик" для преобразования данных:

  1. var migrateDictionary = new SortedDictionary<string, type="">()
  2. {
  3. { "___п_п", typeof(long) },
  4. { "___свид_", typeof(string) },
  5. { "Наименование_СИТ", typeof(string) },
  6. };



Можно расширить словарик до такого вида:

Ключ = Имя исходного поля + его тип
Значение = Имя целевого поля + его тип

3. И непосредственно реализация метода по переносу данных. Ни зависит ни от чего, разве что от ADO.NET, а именно типов DataRow, DataTable, DataSet :)

  1. // Объекты DataRow естественно реально существуют :)
  2. DataRow sourceDataRow = null;
  3. DataRow targetDataRow = null;
  4. foreach (var migrateInfo in migrateDictionary)
  5. {
  6. var columnName = migrateInfo.Key;
  7. var targetType = migrateInfo.Value;
  8. var sourceValue = sourceDataRow[columnName];
  9. targetDataRow[columnName] = ConvertUtils.ToType(sourceValue, targetType);
  10. }



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

Надеюсь что идея понятна. Не сочтите меня за велосипедостроителя. Я знаю что подобных примеров масса в интернете, и просто хочу донести эту мысль до тех, кто читает этот блог :)

2 комментария:

Анонимный комментирует...

ИМХО идея со словариком, нарушает и без того нестрогую енкапсуляцию данных рекорда... Ну или переносит проверку названия колонки в рантайм, а в оригинальном коде это было бы компил тайм...

Второе, это NullReferenceException на originalValue.ToString();. По крайней мере оригинальный код это хендлил.

Хотя вобщемто сложно не согласиться что новый код намого лакончиней и понятней. Я бы наверное разве что реализацию ConvertUtils заменл бы с ифов на хештейбл.

Dictionary[Type, Function[object, object]]() converters = new Dictionary[Type, Function[object, object]]();
converters.Add(typeof(Int32), o => o == null ? String.Empty : Int32.Parse(o.ToString()));

А мож и не заменил бы... Фик его знает, с ифами пойму лучше читаеться...

Alexey Diyan комментирует...

> ИМХО идея со словариком, нарушает и без того нестрогую енкапсуляцию данных рекорда... Ну или переносит проверку названия колонки в рантайм, а в оригинальном коде это было бы компил тайм...

Полностью согласен. Однако в некоторых случаях стоит осознанно идти на компромисс. К тому же никто не запрещает заполнить словарик названиями колонок не в виде строковых литералов, а получить из непосредственно из объектной модели DataTable - использовать типизированный DataColumn.


> Второе, это NullReferenceException на originalValue.ToString();. По крайней мере оригинальный код это хендлил.

Повторюсь, моей целью было показать идею ухода от повторяющихся условных операторов. Я не пытался написать код со всеми проверками и, тем более, не проводил его отладку.


> Хотя вобщемто сложно не согласиться что новый код намого лакончиней и понятней. Я бы наверное разве что реализацию ConvertUtils заменл бы с ифов на хештейбл.

Да, действительно. Идея довольно неплохая. Нужно будет при случае попробовать поиграться с этим подходом.

Спасибо за комментарии.