2011-06-02 6 views
1

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

Я могу прочитать все это, с именами заголовков.

Из этого я могу сгенерировать имя для столбца, который будет использоваться в базе данных Sql CE.

Данные в настоящее время состоит из float, int, DateTime, bit, char и string.

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

Код, указанный ниже, не требуется для чтения, если только кто-то не понимает, что я прошу.

public enum MyParameterType { NA, Float, Bool, Char, Date, Int, String } 

class MyParameter { 

public MyParameter(string name, string value) { 
    if (String.IsNullOrEmpty(name) || String.IsNullOrEmpty(value)) { 
    throw new NotSupportedException("NULL values are not allowed."); 
    } 
    Name = name.Trim(); 
    Value = value.Trim(); 
    Type = MyParameterType.NA; 
    if (-1 < Value.IndexOf('.')) { // try float 
    float f; 
    if (float.TryParse(Value, out f)) { 
     string s = f.ToString(); 
     if (s == Value) { 
     Parameter = new SqlCeParameter(AtName, SqlDbType.Float) { Value = f }; 
     Type = MyParameterType.Float; 
     } 
    } 
    } 
    if (Type == MyParameterType.NA) { 
    bool b; 
    if (bool.TryParse(Value, out b)) { 
     Parameter = new SqlCeParameter(AtName, SqlDbType.Bit) { Value = b }; 
     Type = MyParameterType.Bool; 
    } 
    } 
    if (Type == MyParameterType.NA) { 
    if (Value.Length == 1) { 
     char c = Value[0]; 
     Parameter = new SqlCeParameter(AtName, SqlDbType.Char) { Value = c }; 
     Type = MyParameterType.Char; 
    } 
    } 
    if (Type == MyParameterType.NA) { 
    DateTime date; 
    if (DateTime.TryParse(Value, out date)) { 
     Parameter = new SqlCeParameter(AtName, SqlDbType.DateTime) { Value = date }; 
     Type = MyParameterType.Date; 
    } 
    } 
    if (Type == MyParameterType.NA) { 
    if (50 < Value.Length) { 
     Value = Value.Substring(0, 49); 
    } 
    Parameter = new SqlCeParameter(AtName, SqlDbType.NVarChar, 50) { Value = this.Value }; 
    Type = MyParameterType.String; 
    } 
} 

public string AtName { get { return "@" + Name; } } 

public string Name { get; set; } 

public MyParameterType Type { get; set; } 

public SqlCeParameter Parameter { get; set; } 

public string Value { get; set; } 

} 

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

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

Бонусные баллы за просмотр замечательных новых выражений для создания этого!

+0

[Code Review] (http://codereview.stackexchange.com/), возможно, является более подходящим местом для этого вопроса. –

+3

@Steven: может быть, он мог бы перекрестно опубликовать его, но я думаю, что это достаточно интересно, чтобы оставаться на SO в настоящее время. – user7116

+1

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

ответ

2

Учитывая некоторые абстрактные CsvReader:

using (var reader = new CsvReader(file)) 
{ 
    TableGuess table = new TableGuess { Name = file }; 

    // given: IEnumerable<string> CsvReader.Header { get; } 
    table.AddColumns(reader.Header); 

    string[] parts; 
    while (null != (parts = reader.ReadLine())) 
    { 
     table.AddRow(parts); 
    } 
} 

Ваш ColumnGuess:

class ColumnGuess 
{ 
    public string Name { get; set; } 
    public Type Type { get; set; } 
    public int Samples { get; private set; } 

    public void ImproveType(string value) 
    { 
     if (this.Samples > 10) return; 
     this.Samples++; 

     float f; bool b; DateTime d; int i; 
     if (Single.TryParse(value, out f)) 
     { 
      this.Type = typeof(float); 
     } 
     else if (Boolean.TryParse(value, out b)) 
     { 
      this.Type = typeof(bool); 
     } 
     else if (DateTime.TryParse(value, out d)) 
     { 
      this.Type = typeof(DateTime); 
     } 
     else if (value.Length == 1 && this.Type == null && !Char.IsDigit(value[0])) 
     { 
      this.Type = typeof(char); 
     } 
     else if (this.Type != typeof(float) && Int32.TryParse(value, out i)) 
     { 
      this.Type = typeof(int); 
     } 
    } 
} 

TableGuess будет содержать догадывались столбцы и строки:

class TableGuess 
{ 
    private List<string[]> rows = new List<string[]>(); 
    private List<ColumnGuess> columns; 

    public string Name { get; set; } 

    public void AddColumns(IEnumerable<string> columns) 
    { 
     this.columns = columns.Select(cc => new ColumnGuess { Name = cc }) 
           .ToList(); 
    } 

    public void AddRow(string[] parts) 
    { 
     for (int ii = 0; ii < parts.Length; ++ii) 
     { 
      if (String.IsNullOrEmpty(parts[ii])) continue; 
      columns[ii].ImproveType(parts[ii]); 
     } 

     this.rows.Add(parts); 
    } 
} 

Вы можете добавить TableGuess в AsDataTable() метод:

public DataTable AsDataTable() 
{ 
    var dataTable = new dataTable(this.Name); 
    foreach (var column in this.columns) 
    { 
     dataTable.Columns.Add(new DataColumn(
      column.Name, 
      column.Type ?? typeof(string))); 
    } 

    foreach (var row in this.rows) 
    { 
     object[] values = new object[dataTable.Columns.Count]; 
     for (int cc = 0; cc < row.Length; ++cc) 
     { 
      values[cc] = Convert.ChangeType(row[cc], 
       dataTable.Columns[cc].DataType); 
     } 

     dataTable.LoadRow(values, false); 
    } 

    return dataTable; 
} 

Вы можете использовать SqlCeDataAdapter для перемещения данных в DataTable (после добавления самой таблицы в базе данных).

+0

+1 WOW! Это круто. Позвольте мне взглянуть на это надолго. ;) – jp2code

+1

У меня есть проверка первых 10 записей, которые даются как «ускорение». Но, возможно, стоит дать ему возможность проверить все записи для типов столбцов. Кроме того, в методе 'AsDataTable' вы можете выполнить свою проверку ошибок рядом с' Convert.ChangeType' и вывести/отклонить неверные строки. – user7116

+0

В ** AsDataTable **, какие два "??" в 'column.Type ?? TypeOf (строка))) '? Это новый синтаксис для разрешения по умолчанию? – jp2code

2

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

Может быть с заголовков столбцов можно «разобрать их» к типу ..

и попробовать

Convert.ChangeType(yourValue, typeof(string, double,etc)) 
+0

Я не тестировал это, но будет ли метод ChangeType конвертировать значение? То есть, если 'input =" 1.05 "' и я попробовал 'int num = (int) Convert.ChangeType (input, typeof (int));' ... он выдал бы исключение, установил 'num = 1', или ...? – jp2code

+1

Да, он должен преобразовать его, но с допустимым литом, например Convert.ChangeType ('01/01/20100 ', typeof (DateTime)) или Convert.ChangeType (' 100.25 ', typeof (double)), но только с действительным cast, пример того, что u ave it не будет работать, потому что двойной или плавающий номер не может быть преобразован в int, он выдаст вам исключение –

+0

+1 Спасибо за помощь. – jp2code

1

Как об этом псевдо-код - я считаю, что это должно быть достаточно быстрым для вас. Это очень псевдо-так «строка», «символ» и т. Д. - это просто заполнители для значения перечисления или того, что вам нравится.

For first data row in data file 
    For each column in row 
    TypeOfCol(column) = <best first guess> 
    Next 
Next 

For each data row in data file 
    For each column in row 
    If TypeOfCol(column) = "string" 
     Continue For 
    If TypeOfCol(column) = "char" 
     If columnValue has more than one character 
     TypeOfCol(column) = "string" 
     Continue For 
    If TypeOfCol(column) = "bit" 
     If columnValue isn't 1 or 0 
     TypeOfCol(column) = "int" // Might not be an int - next If will pick up on that... 
    If TypeOfCol(column) = "int" 
     If columnValue isn't integer 
     TypeOfCol(column) = "float" 
    If TypeOfCol(column) = "float" 
     If columnValue isn't a float 
     TypeOfCol(column) = If(columnValue has more than one character then "string" else "char") 
    If TypeOfCol(column) = "datetime" 
     If columnValue isn't a date/time 
     TypeOfCol(column) = "string" 
    Next 
Next 
+0

+1; однако, чтение в строке текста, не должен ли '' string "' test быть мертвым последним по умолчанию? – jp2code

+1

Сначала я проверил строку, как будто вы уже знаете, что столбец является строкой, после чего он не изменится, поэтому не стоит проверять другие типы. 'TypeOfCol' предназначен, например, Словарь, который сохраняет текущий предполагаемый тип для данного столбца. –

+0

+1 ОК, теперь я вижу, что происходит. – jp2code

Смежные вопросы